]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
OFAgent: Implement arp responder
authorfumihiko kakuma <kakuma@valinux.co.jp>
Tue, 22 Apr 2014 00:55:51 +0000 (09:55 +0900)
committerfumihiko kakuma <kakuma@valinux.co.jp>
Mon, 4 Aug 2014 10:39:33 +0000 (19:39 +0900)
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

neutron/agent/l2population_rpc.py
neutron/plugins/ofagent/agent/arp_lib.py [new file with mode: 0644]
neutron/plugins/ofagent/agent/ofa_neutron_agent.py
neutron/plugins/openvswitch/agent/ovs_neutron_agent.py
neutron/tests/unit/agent/l2population_rpc_base.py
neutron/tests/unit/agent/test_l2population_rpc.py
neutron/tests/unit/ofagent/fake_oflib.py
neutron/tests/unit/ofagent/ofa_test_base.py [new file with mode: 0644]
neutron/tests/unit/ofagent/test_arp_lib.py [new file with mode: 0644]
neutron/tests/unit/ofagent/test_ofa_neutron_agent.py

index 3a8ad309fe90e24517ad29b9fb655346abe9b434..315a478c292d4b40be1e6edcf6a5ab208fb65ac3 100644 (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)
diff --git a/neutron/plugins/ofagent/agent/arp_lib.py b/neutron/plugins/ofagent/agent/arp_lib.py
new file mode 100644 (file)
index 0000000..279ad50
--- /dev/null
@@ -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)
index a8bc29d69322dd1a55d612803f3a23910f193cae..9cd285d166b330821664d5b4e2fc6b783a424846 100644 (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)
index d2eec998a62c4529789568b22bbd7e6658c81571..f0c90be672075aa2161bf86f549590b7e2be693f 100644 (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.
-
-        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)
index 563e057ae81abca8333926762a4e9aabf04889c5..6460626283b263c823298ea4c053bddd3e3534de 100644 (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):
 
index 3f542198dbdf1ca2ecf41e36c20929da0bb41693..2b9bbfcab3a8cedb04f75966738403c1d37fa6a0 100644 (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)
index 28a479cb5fcf115a3f32dedf7e7bc0b24b36eae0..01d60d2a4dfc6b84b2e2a7441a1d3f3088b2ec3b 100644 (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,
diff --git a/neutron/tests/unit/ofagent/ofa_test_base.py b/neutron/tests/unit/ofagent/ofa_test_base.py
new file mode 100644 (file)
index 0000000..9b4643a
--- /dev/null
@@ -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 (file)
index 0000000..18078d4
--- /dev/null
@@ -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)
index 438c97564cd142d3c4c8d262d20b50398e6ec1f8..e7974c8f5b3769baf8a62a529095f324bc7c4407 100644 (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)
 
-
-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()