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
'''
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)
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)
--- /dev/null
+# 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)
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
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
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)
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,
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])
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])
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
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'))
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)
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)
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)
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,
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.
-
- 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
+ LOG.debug("update chg_ip received")
+ self.fdb_chg_ip_tun(
+ context, fdb_entries, self.local_ip, self.local_vlan_map)
- 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)
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):
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)
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')
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,
--- /dev/null
+# 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
--- /dev/null
+# 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)
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):
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))
[p_const.TYPE_GRE, p_const.TYPE_VXLAN])
-class TestOFANeutronAgentOVSBridge(OFAAgentTestCase):
+class TestOFANeutronAgentOVSBridge(ofa_test_base.OFAAgentTestBase):
def setUp(self):
super(TestOFANeutronAgentOVSBridge, self).setUp()
self.ovs.setup_ofp()
-class TestOFANeutronAgent(OFAAgentTestCase):
+class TestOFANeutronAgent(ofa_test_base.OFAAgentTestBase):
def setUp(self):
super(TestOFANeutronAgent, self).setUp()
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',
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)
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
_get_ports.assert_called_once_with('hoge')
self.assertEqual(set(names), result)
-
-class AncillaryBridgesTest(OFAAgentTestCase):
+ 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(ofa_test_base.OFAAgentTestBase):
def setUp(self):
super(AncillaryBridgesTest, self).setUp()