OFAgent: Implement arp responder

This is step 2 implementation of OFAgent l2-population.
This handles an arp packet responding locally to an arp request in such a way
that sends an arp request as a packet-in message to controller and
builds and sends an arp reply packet.
Currently this only supports tunnel.

Implements: blueprint ofagent-l2pop

Change-Id: Ida714f30c0f02c54dda3402c0dbf6047bc182b22
This commit is contained in:
fumihiko kakuma 2014-04-22 09:55:51 +09:00
parent 8307be7a9e
commit 9652d2e076
10 changed files with 821 additions and 105 deletions

View File

@ -136,6 +136,22 @@ class L2populationRpcCallBackTunnelMixin(L2populationRpcCallBackMixin):
'''
pass
@abc.abstractmethod
def setup_entry_for_arp_reply(self, action, local_vid, mac_address,
ip_address):
'''Operate the ARP respond information.
Do operation of arp respond information for an action
In ovs do adding or removing flow entry to edit an arp reply.
:param action: an action to operate for arp respond infomation.
"add" or "remove"
:param local_vid: id in local VLAN map of network's ARP entry.
:param mac_address: MAC string value.
:param ip_address: IP string value.
'''
pass
def get_agent_ports(self, fdb_entries, local_vlan_map):
for network_id, values in fdb_entries.items():
lvm = local_vlan_map.get(network_id)
@ -180,3 +196,43 @@ class L2populationRpcCallBackTunnelMixin(L2populationRpcCallBackMixin):
raise NotImplementedError()
getattr(self, method)(context, values)
@log.log
def fdb_chg_ip_tun(self, context, fdb_entries, local_ip, local_vlan_map):
'''fdb update when an IP of a port is updated.
The ML2 l2-pop mechanism driver sends an fdb update rpc message when an
IP of a port is updated.
:param context: RPC context.
:param fdb_entries: fdb dicts that contain all mac/IP informations per
agent and network.
{'net1':
{'agent_ip':
{'before': [[mac, ip]],
'after': [[mac, ip]]
}
}
'net2':
...
}
:param local_ip: local IP address of this agent.
:local_vlan_map: local VLAN map of network.
'''
for network_id, agent_ports in fdb_entries.items():
lvm = local_vlan_map.get(network_id)
if not lvm:
continue
for agent_ip, state in agent_ports.items():
if agent_ip == local_ip:
continue
after = state.get('after')
for mac, ip in after:
self.setup_entry_for_arp_reply('add', lvm.vlan, mac, ip)
before = state.get('before')
for mac, ip in before:
self.setup_entry_for_arp_reply('remove', lvm.vlan, mac, ip)

View File

@ -0,0 +1,190 @@
# Copyright (C) 2014 VA Linux Systems Japan K.K.
#
# 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.
# @author: Fumihiko Kakuma, VA Linux Systems Japan K.K.
# @author: YAMAMOTO Takashi, VA Linux Systems Japan K.K.
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 ryu.ofproto import ether
from neutron.openstack.common import log as logging
from neutron.plugins.openvswitch.common import constants
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 = {}
def _send_arp_reply(self, datapath, port, pkt):
LOG.debug("packet-out %s", 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)
def _add_flow_to_avoid_unknown_packet(self, datapath, match):
LOG.debug("add flow to avoid an unknown packet from packet-in")
ofp = datapath.ofproto
ofpp = datapath.ofproto_parser
instructions = [ofpp.OFPInstructionGotoTable(
table_id=constants.FLOOD_TO_TUN)]
out = ofpp.OFPFlowMod(datapath,
table_id=constants.PATCH_LV_TO_TUN,
command=ofp.OFPFC_ADD,
idle_timeout=5,
priority=20,
match=match,
instructions=instructions)
ryu_api.send_msg(self.ryuapp, out)
def _send_unknown_packet(self, msg, in_port, out_port):
LOG.debug("unknown packet-out in-port %(in_port)s "
"out-port %(out_port)s msg %(msg)s",
{'in_port': in_port, 'out_port': out_port, 'msg': msg})
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))
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
def add_arp_table_entry(self, network, ip, mac):
LOG.debug("added arp table entry: "
"network %(network)s ip %(ip)s mac %(mac)s",
{'network': network, 'ip': ip, 'mac': mac})
if network in self._arp_tbl:
self._arp_tbl[network][ip] = mac
else:
self._arp_tbl[network] = {ip: mac}
def del_arp_table_entry(self, network, ip):
LOG.debug("deleted arp table entry: network %(network)s ip %(ip)s",
{'network': network, 'ip': ip})
del self._arp_tbl[network][ip]
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
ofp = datapath.ofproto
ofpp = datapath.ofproto_parser
port = msg.match['in_port']
pkt = packet.Packet(msg.data)
LOG.info(_("packet-in dpid %(dpid)s in_port %(port)s pkt %(pkt)s"),
{'dpid': dpid_lib.dpid_to_str(datapath.id),
'port': port, 'pkt': pkt})
pkt_vlan = None
pkt_arp = None
pkt_ethernet = pkt.get_protocol(ethernet.ethernet)
if not pkt_ethernet:
LOG.info(_("non-ethernet packet"))
else:
pkt_vlan = pkt.get_protocol(vlan.vlan)
if not pkt_vlan:
LOG.info(_("non-vlan packet"))
if pkt_vlan:
network = pkt_vlan.vid
pkt_arp = pkt.get_protocol(arp.arp)
if not pkt_arp:
LOG.info(_("drop non-arp packet"))
return
else:
# drop an unknown packet.
LOG.info(_("drop unknown 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(_("unknown network %s"), network)
# add a flow to skip a packet-in to a controller.
match = ofpp.OFPMatch(eth_type=ether.ETH_TYPE_ARP,
vlan_vid=network | ofp.OFPVID_PRESENT,
arp_op=arp.ARP_REQUEST,
arp_tpa=pkt_arp.dst_ip)
self._add_flow_to_avoid_unknown_packet(datapath, match)
# send an unknown arp packet to the table.
self._send_unknown_packet(msg, port, ofp.OFPP_TABLE)

View File

@ -23,7 +23,11 @@ import netaddr
from oslo.config import cfg
from ryu.app.ofctl import api as ryu_api
from ryu.base import app_manager
from ryu.controller import handler
from ryu.controller import ofp_event
from ryu.lib import hub
from ryu.lib.packet import arp
from ryu.ofproto import ether
from ryu.ofproto import ofproto_v1_3 as ryu_ofp13
from neutron.agent import l2population_rpc
@ -41,6 +45,7 @@ from neutron import context
from neutron.openstack.common import log as logging
from neutron.openstack.common import loopingcall
from neutron.plugins.common import constants as p_const
from neutron.plugins.ofagent.agent import arp_lib
from neutron.plugins.ofagent.agent import ports
from neutron.plugins.ofagent.common import config # noqa
from neutron.plugins.openvswitch.common import constants
@ -132,8 +137,11 @@ class OFASecurityGroupAgent(sg_rpc.SecurityGroupAgentRpcMixin):
class OFANeutronAgentRyuApp(app_manager.RyuApp):
OFP_VERSIONS = [ryu_ofp13.OFP_VERSION]
def start(self):
def __init__(self, *args, **kwargs):
super(OFANeutronAgentRyuApp, self).__init__(*args, **kwargs)
self.arplib = arp_lib.ArpLib(self)
def start(self):
super(OFANeutronAgentRyuApp, self).start()
return hub.spawn(self._agent_main, self)
@ -160,6 +168,16 @@ class OFANeutronAgentRyuApp(app_manager.RyuApp):
LOG.info(_("Agent initialized successfully, now running... "))
agent.daemon_loop()
@handler.set_ev_cls(ofp_event.EventOFPPacketIn, handler.MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
self.arplib.packet_in_handler(ev)
def add_arp_table_entry(self, network, ip, mac):
self.arplib.add_arp_table_entry(network, ip, mac)
def del_arp_table_entry(self, network, ip):
self.arplib.del_arp_table_entry(network, ip)
class OFANeutronAgent(n_rpc.RpcCallback,
sg_rpc.SecurityGroupAgentRpcCallbackMixin,
@ -391,6 +409,8 @@ class OFANeutronAgent(n_rpc.RpcCallback,
lvm.tun_ofports.add(ofport)
self._add_fdb_flooding_flow(lvm)
else:
self.ryuapp.add_arp_table_entry(
lvm.vlan, port_info[1], port_info[0])
match = ofpp.OFPMatch(
vlan_vid=int(lvm.vlan) | ofp.OFPVID_PRESENT,
eth_dst=port_info[0])
@ -427,6 +447,7 @@ class OFANeutronAgent(n_rpc.RpcCallback,
match=match)
self.ryu_send_msg(msg)
else:
self.ryuapp.del_arp_table_entry(lvm.vlan, port_info[1])
match = ofpp.OFPMatch(
vlan_vid=int(lvm.vlan) | ofp.OFPVID_PRESENT,
eth_dst=port_info[0])
@ -438,6 +459,18 @@ class OFANeutronAgent(n_rpc.RpcCallback,
match=match)
self.ryu_send_msg(msg)
def setup_entry_for_arp_reply(self, action, local_vid, mac_address,
ip_address):
if action == 'add':
self.ryuapp.add_arp_table_entry(local_vid, ip_address, mac_address)
elif action == 'remove':
self.ryuapp.del_arp_table_entry(local_vid, ip_address)
def _fdb_chg_ip(self, context, fdb_entries):
LOG.debug("update chg_ip received")
self.fdb_chg_ip_tun(
context, fdb_entries, self.local_ip, self.local_vlan_map)
def _provision_local_vlan_inbound_for_tunnel(self, lvid, network_type,
segmentation_id):
br = self.tun_br
@ -792,6 +825,22 @@ class OFANeutronAgent(n_rpc.RpcCallback,
msg = br.ofparser.OFPFlowMod(br.datapath, priority=0)
self.ryu_send_msg(msg)
def _tun_br_output_arp_packet_to_controller(self, br):
datapath = br.datapath
ofp = datapath.ofproto
ofpp = datapath.ofproto_parser
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(datapath,
table_id=constants.PATCH_LV_TO_TUN,
priority=10,
match=match,
instructions=instructions)
self.ryu_send_msg(msg)
def _tun_br_goto_table_ucast_unicast(self, br):
match = br.ofparser.OFPMatch(eth_dst=('00:00:00:00:00:00',
'01:00:00:00:00:00'))
@ -799,6 +848,7 @@ class OFANeutronAgent(n_rpc.RpcCallback,
table_id=constants.UCAST_TO_TUN)]
msg = br.ofparser.OFPFlowMod(br.datapath,
table_id=constants.PATCH_LV_TO_TUN,
priority=0,
match=match,
instructions=instructions)
self.ryu_send_msg(msg)
@ -810,6 +860,7 @@ class OFANeutronAgent(n_rpc.RpcCallback,
table_id=constants.FLOOD_TO_TUN)]
msg = br.ofparser.OFPFlowMod(br.datapath,
table_id=constants.PATCH_LV_TO_TUN,
priority=0,
match=match,
instructions=instructions)
self.ryu_send_msg(msg)
@ -879,6 +930,7 @@ class OFANeutronAgent(n_rpc.RpcCallback,
self.ryu_send_msg(msg)
self._tun_br_sort_incoming_traffic_depend_in_port(self.tun_br)
self._tun_br_output_arp_packet_to_controller(self.tun_br)
self._tun_br_goto_table_ucast_unicast(self.tun_br)
self._tun_br_goto_table_flood_broad_multi_cast(self.tun_br)
self._tun_br_set_table_tun_by_tunnel_type(self.tun_br)

View File

@ -369,8 +369,8 @@ class OVSNeutronAgent(n_rpc.RpcCallback,
actions="strip_vlan,set_tunnel:%s,"
"output:%s" % (lvm.segmentation_id, ofports))
else:
self._set_arp_responder('add', lvm.vlan, port_info[0],
port_info[1])
self.setup_entry_for_arp_reply('add', lvm.vlan, port_info[0],
port_info[1])
if not self.dvr_agent.is_dvr_router_interface(port_info[1]):
self.tun_br.add_flow(table=constants.UCAST_TO_TUN,
priority=2,
@ -395,83 +395,43 @@ class OVSNeutronAgent(n_rpc.RpcCallback,
self.tun_br.delete_flows(table=constants.FLOOD_TO_TUN,
dl_vlan=lvm.vlan)
else:
self._set_arp_responder('remove', lvm.vlan, port_info[0],
port_info[1])
self.setup_entry_for_arp_reply('remove', lvm.vlan, port_info[0],
port_info[1])
self.tun_br.delete_flows(table=constants.UCAST_TO_TUN,
dl_vlan=lvm.vlan,
dl_dst=port_info[0])
def _fdb_chg_ip(self, context, fdb_entries):
'''fdb update when an IP of a port is updated.
LOG.debug("update chg_ip received")
self.fdb_chg_ip_tun(
context, fdb_entries, self.local_ip, self.local_vlan_map)
The ML2 l2-pop mechanism driver send an fdb update rpc message when an
IP of a port is updated.
:param context: RPC context.
:param fdb_entries: fdb dicts that contain all mac/IP informations per
agent and network.
{'net1':
{'agent_ip':
{'before': [[mac, ip]],
'after': [[mac, ip]]
}
}
'net2':
...
}
'''
LOG.debug(_("update chg_ip received"))
# TODO(ethuleau): Use OVS defer apply flows for all rules will be an
# interesting improvement here. But actually, OVS lib defer apply flows
# methods doesn't ensure the add flows will be applied before delete.
for network_id, agent_ports in fdb_entries.items():
lvm = self.local_vlan_map.get(network_id)
if not lvm:
continue
for agent_ip, state in agent_ports.items():
if agent_ip == self.local_ip:
continue
after = state.get('after')
for mac, ip in after:
self._set_arp_responder('add', lvm.vlan, mac, ip)
before = state.get('before')
for mac, ip in before:
self._set_arp_responder('remove', lvm.vlan, mac, ip)
def _set_arp_responder(self, action, lvid, mac_str, ip_str):
def setup_entry_for_arp_reply(self, action, local_vid, mac_address,
ip_address):
'''Set the ARP respond entry.
When the l2 population mechanism driver and OVS supports to edit ARP
fields, a table (ARP_RESPONDER) to resolve ARP locally is added to the
tunnel bridge.
:param action: add or remove ARP entry.
:param lvid: local VLAN map of network's ARP entry.
:param mac_str: MAC string value.
:param ip_str: IP string value.
'''
if not self.arp_responder_enabled:
return
mac = netaddr.EUI(mac_str, dialect=netaddr.mac_unix)
ip = netaddr.IPAddress(ip_str)
mac = netaddr.EUI(mac_address, dialect=netaddr.mac_unix)
ip = netaddr.IPAddress(ip_address)
if action == 'add':
actions = constants.ARP_RESPONDER_ACTIONS % {'mac': mac, 'ip': ip}
self.tun_br.add_flow(table=constants.ARP_RESPONDER,
priority=1,
proto='arp',
dl_vlan=lvid,
dl_vlan=local_vid,
nw_dst='%s' % ip,
actions=actions)
elif action == 'remove':
self.tun_br.delete_flows(table=constants.ARP_RESPONDER,
proto='arp',
dl_vlan=lvid,
dl_vlan=local_vid,
nw_dst='%s' % ip)
else:
LOG.warning(_('Action %s not supported'), action)

View File

@ -40,6 +40,10 @@ class FakeNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin):
def cleanup_tunnel_port(self, tun_ofport, tunnel_type):
pass
def setup_entry_for_arp_reply(self, action, local_vid, mac_address,
ip_address):
pass
class TestL2populationRpcCallBackTunnelMixinBase(base.BaseTestCase):

View File

@ -188,3 +188,46 @@ class TestL2populationRpcCallBackTunnelMixin(
self.assertRaises(NotImplementedError,
self.fakeagent.fdb_update,
'context', self.upd_fdb_entry1)
def test__fdb_chg_ip(self):
m_setup_entry_for_arp_reply = mock.Mock()
self.fakeagent.setup_entry_for_arp_reply = m_setup_entry_for_arp_reply
self.fakeagent.fdb_chg_ip_tun('context', self.upd_fdb_entry1_val,
self.local_ip, self.local_vlan_map1)
expected = [
mock.call('remove', self.lvm1.vlan, self.lvms[0].mac,
self.lvms[0].ip),
mock.call('add', self.lvm1.vlan, self.lvms[1].mac,
self.lvms[1].ip),
mock.call('remove', self.lvm1.vlan, self.lvms[0].mac,
self.lvms[0].ip),
mock.call('add', self.lvm1.vlan, self.lvms[1].mac,
self.lvms[1].ip),
mock.call('remove', self.lvm2.vlan, self.lvms[0].mac,
self.lvms[0].ip),
mock.call('add', self.lvm2.vlan, self.lvms[2].mac,
self.lvms[2].ip),
]
m_setup_entry_for_arp_reply.assert_has_calls(expected, any_order=True)
def test__fdb_chg_ip_no_lvm(self):
m_setup_entry_for_arp_reply = mock.Mock()
self.fakeagent.setup_entry_for_arp_reply = m_setup_entry_for_arp_reply
self.fakeagent.fdb_chg_ip_tun(
'context', self.upd_fdb_entry1, self.local_ip, {})
self.assertFalse(m_setup_entry_for_arp_reply.call_count)
def test__fdb_chg_ip_ip_is_local_ip(self):
upd_fdb_entry_val = {
self.lvms[0].net: {
self.local_ip: {
'before': [[self.lvms[0].mac, self.lvms[0].ip]],
'after': [[self.lvms[1].mac, self.lvms[1].ip]],
},
},
}
m_setup_entry_for_arp_reply = mock.Mock()
self.fakeagent.setup_entry_for_arp_reply = m_setup_entry_for_arp_reply
self.fakeagent.fdb_chg_ip_tun('context', upd_fdb_entry_val,
self.local_ip, self.local_vlan_map1)
self.assertFalse(m_setup_entry_for_arp_reply.call_count)

View File

@ -98,8 +98,24 @@ class _Mod(object):
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
ofp = _Mod('ryu.ofproto.ofproto_v1_3')
ofpp = _Mod('ryu.ofproto.ofproto_v1_3_parser')
@ -110,8 +126,18 @@ def patch_fake_oflib_of():
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.ofproto_v1_3': ofp,
'ryu.ofproto.ofproto_v1_3_parser': ofpp,

View File

@ -0,0 +1,65 @@
# Copyright (C) 2014 VA Linux Systems Japan K.K.
#
# 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.
#
# @author: Fumihiko Kakuma, VA Linux Systems Japan K.K.
# @author: YAMAMOTO Takashi, VA Linux Systems Japan K.K.
import mock
from oslo.config import cfg
from neutron.openstack.common import importutils
from neutron.tests import base
from neutron.tests.unit.ofagent import fake_oflib
class OFAAgentTestBase(base.BaseTestCase):
_AGENT_NAME = 'neutron.plugins.ofagent.agent.ofa_neutron_agent'
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)
self.mod_agent = importutils.import_module(self._AGENT_NAME)
super(OFAAgentTestBase, self).setUp()
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')
])
cfg.CONF.set_override('root_helper', 'fake_helper', group='AGENT')
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

View File

@ -0,0 +1,309 @@
# Copyright (C) 2014 VA Linux Systems Japan K.K.
#
# 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.
# @author: Fumihiko Kakuma, VA Linux Systems Japan K.K.
import collections
import contextlib
import mock
from neutron.openstack.common import importutils
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 = self.nets[0].net
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}
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
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_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.arplib,
'_add_flow_to_avoid_unknown_packet'),
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, self.proto_arp_mod)
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.arplib,
'_add_flow_to_avoid_unknown_packet'),
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_non_ethernet(self):
self._fake_get_protocol_ethernet = False
self._test_packet_in_handler()
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()
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.arplib,
'_add_flow_to_avoid_unknown_packet'),
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(
self.datapath,
self.datapath.ofproto_parser.OFPMatch(
eth_type=self.ethernet.ETH_TYPE_ARP,
vlan_vid=self.proto_vlan_mod.vid |
self.datapath.ofproto.OFPVID_PRESENT,
arp_op=self.arp.ARP_REQUEST,
arp_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)

View File

@ -32,12 +32,10 @@ from neutron.common import constants as n_const
from neutron.openstack.common import importutils
from neutron.plugins.common import constants as p_const
from neutron.plugins.openvswitch.common import constants
from neutron.tests import base
from neutron.tests.unit.ofagent import fake_oflib
from neutron.tests.unit.ofagent import ofa_test_base
NOTIFIER = ('neutron.plugins.ml2.rpc.AgentNotifierApi')
OVS_LINUX_KERN_VERS_WITHOUT_VXLAN = "3.12.0"
def _mock_port(is_neutron=True, normalized_name=None):
@ -48,30 +46,7 @@ def _mock_port(is_neutron=True, normalized_name=None):
return p
class OFAAgentTestCase(base.BaseTestCase):
_AGENT_NAME = 'neutron.plugins.ofagent.agent.ofa_neutron_agent'
def setUp(self):
super(OFAAgentTestCase, self).setUp()
self.fake_oflib_of = fake_oflib.patch_fake_oflib_of().start()
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')
])
cfg.CONF.set_override('root_helper', 'fake_helper', group='AGENT')
class CreateAgentConfigMap(OFAAgentTestCase):
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))
@ -116,7 +91,7 @@ class CreateAgentConfigMap(OFAAgentTestCase):
[p_const.TYPE_GRE, p_const.TYPE_VXLAN])
class TestOFANeutronAgentOVSBridge(OFAAgentTestCase):
class TestOFANeutronAgentOVSBridge(ofa_test_base.OFAAgentTestBase):
def setUp(self):
super(TestOFANeutronAgentOVSBridge, self).setUp()
@ -209,7 +184,7 @@ class TestOFANeutronAgentOVSBridge(OFAAgentTestCase):
self.ovs.setup_ofp()
class TestOFANeutronAgent(OFAAgentTestCase):
class TestOFANeutronAgent(ofa_test_base.OFAAgentTestBase):
def setUp(self):
super(TestOFANeutronAgent, self).setUp()
@ -229,23 +204,6 @@ class TestOFANeutronAgent(OFAAgentTestCase):
def start(self, interval=0):
self.f()
def _mk_test_dp(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__ = lambda _self: name
return dp
def _mk_test_br(name):
dp = _mk_test_dp(name)
br = mock.Mock()
br.datapath = dp
br.ofproto = dp.ofproto
br.ofparser = dp.ofproto_parser
return br
with contextlib.nested(
mock.patch.object(self.mod_agent.OFANeutronAgent,
'setup_integration_br',
@ -264,14 +222,14 @@ class TestOFANeutronAgent(OFAAgentTestCase):
self.agent = self.mod_agent.OFANeutronAgent(self.ryuapp, **kwargs)
self.agent.sg_agent = mock.Mock()
self.int_dp = _mk_test_dp('int_br')
self.int_dp = self._mk_test_dp('int_br')
self.agent.int_br.ofparser = self.int_dp.ofproto_parser
self.agent.int_br.datapath = self.int_dp
self.agent.tun_br = _mk_test_br('tun_br')
self.agent.phys_brs['phys-net1'] = _mk_test_br('phys_br1')
self.agent.tun_br = self._mk_test_br('tun_br')
self.agent.phys_brs['phys-net1'] = self._mk_test_br('phys_br1')
self.agent.phys_ofports['phys-net1'] = 777
self.agent.int_ofports['phys-net1'] = 666
self.datapath = _mk_test_dp('phys_br')
self.datapath = self._mk_test_dp('phys_br')
def _create_tunnel_port_name(self, tunnel_ip, tunnel_type):
tunnel_ip_hex = '%08x' % netaddr.IPAddress(tunnel_ip, version=4)
@ -796,6 +754,44 @@ class TestOFANeutronAgent(OFAAgentTestCase):
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: [['mac1', 'ip1']],
self.lvms[1].ip: [['mac2', 'ip2']]}}}
with mock.patch.multiple(self.agent,
ryu_send_msg=mock.DEFAULT,
setup_tunnel_port=mock.DEFAULT):
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)
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: [['mac1', 'ip1']],
self.lvms[1].ip: [['mac2', 'ip2']]}}}
with mock.patch.multiple(self.agent,
ryu_send_msg=mock.DEFAULT,
setup_tunnel_port=mock.DEFAULT):
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)
def test_recl_lv_port_to_preserve(self):
self._prepare_l2_pop_ofports()
self.agent.enable_tunneling = True
@ -1119,8 +1115,23 @@ class TestOFANeutronAgent(OFAAgentTestCase):
_get_ports.assert_called_once_with('hoge')
self.assertEqual(set(names), result)
def test_setup_tunnel_br(self):
with contextlib.nested(
mock.patch.object(self.agent.int_br,
'add_patch_port', return_value='1'),
mock.patch.object(self.agent.tun_br,
'add_patch_port', return_value='2'),
mock.patch.object(self.mod_agent, 'OVSBridge',
return_value=self.agent.tun_br),
mock.patch.object(self.agent,
'_tun_br_output_arp_packet_to_controller')
) as (int_add_patch_port, tun_add_patch_port,
ovs_br_class, tun_output_ctrl):
self.agent.setup_tunnel_br(cfg.CONF.OVS.tunnel_bridge)
tun_output_ctrl.assert_called_once_with(self.agent.tun_br)
class AncillaryBridgesTest(OFAAgentTestCase):
class AncillaryBridgesTest(ofa_test_base.OFAAgentTestBase):
def setUp(self):
super(AncillaryBridgesTest, self).setUp()