From: fumihiko kakuma Date: Tue, 22 Apr 2014 00:55:51 +0000 (+0900) Subject: OFAgent: Implement arp responder X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=9652d2e076aad2864d19db77c9b403f9e81e1f24;p=openstack-build%2Fneutron-build.git 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 --- diff --git a/neutron/agent/l2population_rpc.py b/neutron/agent/l2population_rpc.py index 3a8ad309f..315a478c2 100644 --- a/neutron/agent/l2population_rpc.py +++ b/neutron/agent/l2population_rpc.py @@ -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) diff --git a/neutron/plugins/ofagent/agent/arp_lib.py b/neutron/plugins/ofagent/agent/arp_lib.py new file mode 100644 index 000000000..279ad5019 --- /dev/null +++ b/neutron/plugins/ofagent/agent/arp_lib.py @@ -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) diff --git a/neutron/plugins/ofagent/agent/ofa_neutron_agent.py b/neutron/plugins/ofagent/agent/ofa_neutron_agent.py index a8bc29d69..9cd285d16 100644 --- a/neutron/plugins/ofagent/agent/ofa_neutron_agent.py +++ b/neutron/plugins/ofagent/agent/ofa_neutron_agent.py @@ -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) diff --git a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py index d2eec998a..f0c90be67 100644 --- a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py @@ -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. - - 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) diff --git a/neutron/tests/unit/agent/l2population_rpc_base.py b/neutron/tests/unit/agent/l2population_rpc_base.py index 563e057ae..646062628 100644 --- a/neutron/tests/unit/agent/l2population_rpc_base.py +++ b/neutron/tests/unit/agent/l2population_rpc_base.py @@ -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): diff --git a/neutron/tests/unit/agent/test_l2population_rpc.py b/neutron/tests/unit/agent/test_l2population_rpc.py index 3f542198d..2b9bbfcab 100644 --- a/neutron/tests/unit/agent/test_l2population_rpc.py +++ b/neutron/tests/unit/agent/test_l2population_rpc.py @@ -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) diff --git a/neutron/tests/unit/ofagent/fake_oflib.py b/neutron/tests/unit/ofagent/fake_oflib.py index 28a479cb5..01d60d2a4 100644 --- a/neutron/tests/unit/ofagent/fake_oflib.py +++ b/neutron/tests/unit/ofagent/fake_oflib.py @@ -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, diff --git a/neutron/tests/unit/ofagent/ofa_test_base.py b/neutron/tests/unit/ofagent/ofa_test_base.py new file mode 100644 index 000000000..9b4643a20 --- /dev/null +++ b/neutron/tests/unit/ofagent/ofa_test_base.py @@ -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 diff --git a/neutron/tests/unit/ofagent/test_arp_lib.py b/neutron/tests/unit/ofagent/test_arp_lib.py new file mode 100644 index 000000000..18078d464 --- /dev/null +++ b/neutron/tests/unit/ofagent/test_arp_lib.py @@ -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) diff --git a/neutron/tests/unit/ofagent/test_ofa_neutron_agent.py b/neutron/tests/unit/ofagent/test_ofa_neutron_agent.py index 438c97564..e7974c8f5 100644 --- a/neutron/tests/unit/ofagent/test_ofa_neutron_agent.py +++ b/neutron/tests/unit/ofagent/test_ofa_neutron_agent.py @@ -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) - -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()