diff --git a/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini b/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini index 50ec5565797..1718d4dd60e 100644 --- a/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini +++ b/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini @@ -128,6 +128,11 @@ # # l2_population = False +# Enable local ARP responder. Requires OVS 2.1. This is only used by the l2 +# population ML2 MechanismDriver. +# +# arp_responder = False + [securitygroup] # Firewall driver for realizing neutron security group function. # firewall_driver = neutron.agent.firewall.NoopFirewallDriver diff --git a/neutron/agent/linux/ovs_lib.py b/neutron/agent/linux/ovs_lib.py index 5caa4204514..97e9af2cb5f 100644 --- a/neutron/agent/linux/ovs_lib.py +++ b/neutron/agent/linux/ovs_lib.py @@ -22,6 +22,7 @@ from oslo.config import cfg from neutron.agent.linux import ip_lib from neutron.agent.linux import utils from neutron.common import exceptions +from neutron.common import utils as common_utils from neutron.openstack.common import excutils from neutron.openstack.common import jsonutils from neutron.openstack.common import log as logging @@ -586,3 +587,26 @@ def _build_flow_expr_str(flow_dict, cmd): flow_expr_arr.append(actions) return ','.join(flow_expr_arr) + + +def ofctl_arg_supported(root_helper, cmd, args): + '''Verify if ovs-ofctl binary supports command with specific args. + + :param root_helper: utility to use when running shell cmds. + :param cmd: ovs-vsctl command to use for test. + :param args: arguments to test with command. + :returns: a boolean if the args supported. + ''' + supported = True + br_name = 'br-test-%s' % common_utils.get_random_string(6) + test_br = OVSBridge(br_name, root_helper) + test_br.reset_bridge() + + full_args = ["ovs-ofctl", cmd, test_br.br_name] + args + try: + utils.execute(full_args, root_helper=root_helper) + except Exception: + supported = False + + test_br.destroy() + return supported diff --git a/neutron/common/utils.py b/neutron/common/utils.py index d72a3ed1c3a..5b0d38a1d24 100644 --- a/neutron/common/utils.py +++ b/neutron/common/utils.py @@ -18,8 +18,11 @@ """Utilities and helper functions.""" +import datetime +import hashlib import logging as std_logging import os +import random import signal import socket @@ -199,3 +202,17 @@ def log_opt_values(log): def is_valid_vlan_tag(vlan): return q_const.MIN_VLAN_TAG <= vlan <= q_const.MAX_VLAN_TAG + + +def get_random_string(length): + """Get a random hex string of the specified length. + + based on Cinder library + cinder/transfer/api.py + """ + rndstr = "" + random.seed(datetime.datetime.now().microsecond) + while len(rndstr) < length: + rndstr += hashlib.sha224(str(random.random())).hexdigest() + + return rndstr[0:length] diff --git a/neutron/plugins/ml2/drivers/l2pop/README b/neutron/plugins/ml2/drivers/l2pop/README new file mode 100644 index 00000000000..46bb27e5415 --- /dev/null +++ b/neutron/plugins/ml2/drivers/l2pop/README @@ -0,0 +1,41 @@ +Neutron ML2 l2 population Mechanism Drivers + +l2 population (l2pop) mechanism drivers implements the ML2 driver to improve +open source plugins overlay implementations (VXLAN with Linux bridge and +GRE/VXLAN with OVS). This mechanism driver is implemented in ML2 to propagate +the forwarding information among agents using a common RPC API. + +More informations could be found on the wiki page [1]. + +VXLAN Linux kernel: +------------------- +The VXLAN Linux kernel module provide all necessary functionalities to populate +the forwarding table and local ARP responder tables. This module appears on +release 3.7 of the vanilla Linux kernel in experimental: +- 3.8: first stable release, no edge replication (multicast necessary), +- 3.9: edge replication only for the broadcasted packets, +- 3.11: edge replication for broadcast, multicast and unknown packets. + +Note: Some distributions (like RHEL) have backported this module on precedent + kernel version. + +OpenvSwitch: +------------ +The OVS OpenFlow tables provide all of the necessary functionality to populate +the forwarding table and local ARP responder tables. +A wiki page describe how the flow tables did evolve on OVS agents: +- [2] without local ARP responder +- [3] with local ARP responder. /!\ This functionality is only available since + the development branch 2.1. It's possible + to disable (enable by default) it through + the flag 'arp_responder'. /!\ + + +Note: A difference persists between the LB and OVS agents when they are used + with the l2-pop mechanism driver (and local ARP responder available). The + LB agent will drop unknown unicast (VXLAN bridge mode), whereas the OVS + agent will flood it. + +[1] https://wiki.openstack.org/wiki/L2population_blueprint +[2] https://wiki.openstack.org/wiki/Ovs-flow-logic#OVS_flows_logic +[3] https://wiki.openstack.org/wiki/Ovs-flow-logic#OVS_flows_logic_with_local_ARP_responder \ No newline at end of file diff --git a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py index 70115217314..fd565bc0944 100644 --- a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py @@ -152,7 +152,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, veth_mtu=None, l2_population=False, minimize_polling=False, ovsdb_monitor_respawn_interval=( - constants.DEFAULT_OVSDBMON_RESPAWN)): + constants.DEFAULT_OVSDBMON_RESPAWN), + arp_responder=False): '''Constructor. :param integ_br: name of the integration bridge. @@ -170,6 +171,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, :param ovsdb_monitor_respawn_interval: Optional, when using polling minimization, the number of seconds to wait before respawning the ovsdb monitor. + :param arp_responder: Optional, enable local ARP responder if it is + supported. ''' self.veth_mtu = veth_mtu self.root_helper = root_helper @@ -177,6 +180,11 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, q_const.MAX_VLAN_TAG)) self.tunnel_types = tunnel_types or [] self.l2_pop = l2_population + # TODO(ethuleau): Initially, local ARP responder is be dependent to the + # ML2 l2 population mechanism driver. + self.arp_responder_enabled = (arp_responder and + self._check_arp_responder_support() and + self.l2_pop) self.agent_state = { 'binary': 'neutron-openvswitch-agent', 'host': cfg.CONF.host, @@ -184,7 +192,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, 'configurations': {'bridge_mappings': bridge_mappings, 'tunnel_types': self.tunnel_types, 'tunneling_ip': local_ip, - 'l2_population': self.l2_pop}, + 'l2_population': self.l2_pop, + 'arp_responder_enabled': + self.arp_responder_enabled}, 'agent_type': q_const.AGENT_TYPE_OVS, 'start_flag': True} @@ -233,6 +243,20 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, LOG.exception(_("Agent terminated")) raise SystemExit(1) + def _check_arp_responder_support(self): + '''Check if OVS supports to modify ARP headers. + + This functionality is only available since the development branch 2.1. + ''' + args = ['arp,action=load:0x2->NXM_OF_ARP_OP[],' + 'move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],' + 'move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[]'] + supported = ovs_lib.ofctl_arg_supported(self.root_helper, 'add-flow', + args) + if not supported: + LOG.warning(_('OVS version can not support ARP responder.')) + return supported + def _report_state(self): # How many devices are likely used by a VM self.agent_state.get('configurations')['devices'] = ( @@ -375,7 +399,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, actions="strip_vlan,set_tunnel:%s," "output:%s" % (lvm.segmentation_id, ofports)) else: - # TODO(feleouet): add ARP responder entry + self._set_arp_responder('add', lvm.vlan, port_info[0], + port_info[1]) self.tun_br.add_flow(table=constants.UCAST_TO_TUN, priority=2, dl_vlan=lvm.vlan, @@ -400,11 +425,53 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, # Check if this tunnel port is still used self.cleanup_tunnel_port(ofport, lvm.network_type) else: - #TODO(feleouet): remove ARP responder entry + self._set_arp_responder('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 + + 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 fdb_update(self, context, fdb_entries): LOG.debug(_("fdb_update received")) for action, values in fdb_entries.items(): @@ -414,6 +481,47 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, getattr(self, method)(context, values) + def _set_arp_responder(self, action, lvid, mac_str, ip_str): + '''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) + + if action == 'add': + actions = ('move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],' + 'mod_dl_src:%(mac)s,' + 'load:0x2->NXM_OF_ARP_OP[],' + 'move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],' + 'move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],' + 'load:%(mac)#x->NXM_NX_ARP_SHA[],' + 'load:%(ip)#x->NXM_OF_ARP_SPA[],' + 'in_port' % {'mac': mac, 'ip': ip}) + self.tun_br.add_flow(table=constants.ARP_RESPONDER, + priority=1, + proto='arp', + dl_vlan=lvid, + nw_dst='%s' % ip, + actions=actions) + elif action == 'remove': + self.tun_br.delete_flows(table=constants.ARP_RESPONDER, + proto='arp', + dl_vlan=lvid, + nw_dst='%s' % ip) + else: + LOG.warning(_('Action %s not supported'), action) + def create_rpc_dispatcher(self): '''Get the rpc dispatcher for this manager. @@ -701,13 +809,24 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, actions="resubmit(,%s)" % constants.PATCH_LV_TO_TUN) self.tun_br.add_flow(priority=0, actions="drop") + if self.arp_responder_enabled: + # ARP broadcast-ed request go to the local ARP_RESPONDER table to + # be locally resolved + self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN, + priority=1, + proto='arp', + dl_dst="ff:ff:ff:ff:ff:ff", + actions=("resubmit(,%s)" % + constants.ARP_RESPONDER)) # PATCH_LV_TO_TUN table will handle packets coming from patch_int # unicasts go to table UCAST_TO_TUN where remote adresses are learnt self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN, + priority=0, dl_dst="00:00:00:00:00:00/01:00:00:00:00:00", actions="resubmit(,%s)" % constants.UCAST_TO_TUN) # Broadcasts/multicasts go to table FLOOD_TO_TUN that handles flooding self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN, + priority=0, dl_dst="01:00:00:00:00:00/01:00:00:00:00:00", actions="resubmit(,%s)" % constants.FLOOD_TO_TUN) # Tables [tunnel_type]_TUN_TO_LV will set lvid depending on tun_id @@ -742,6 +861,13 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, priority=0, actions="resubmit(,%s)" % constants.FLOOD_TO_TUN) + if self.arp_responder_enabled: + # If none of the ARP entries correspond to the requested IP, the + # broadcast-ed packet is resubmitted to the flooding table + self.tun_br.add_flow(table=constants.ARP_RESPONDER, + priority=0, + actions="resubmit(,%s)" % + constants.FLOOD_TO_TUN) # FLOOD_TO_TUN will handle flooding in tunnels based on lvid, # for now, add a default drop action self.tun_br.add_flow(table=constants.FLOOD_TO_TUN, @@ -1318,6 +1444,7 @@ def create_agent_config_map(config): tunnel_types=config.AGENT.tunnel_types, veth_mtu=config.AGENT.veth_mtu, l2_population=config.AGENT.l2_population, + arp_responder=config.AGENT.arp_responder, ) # If enable_tunneling is TRUE, set tunnel_type to default to GRE diff --git a/neutron/plugins/openvswitch/common/config.py b/neutron/plugins/openvswitch/common/config.py index 038b3473117..b3c332dce78 100644 --- a/neutron/plugins/openvswitch/common/config.py +++ b/neutron/plugins/openvswitch/common/config.py @@ -80,6 +80,8 @@ agent_opts = [ cfg.BoolOpt('l2_population', default=False, help=_("Use ml2 l2population mechanism driver to learn " "remote mac and IPs and improve tunnel scalability")), + cfg.BoolOpt('arp_responder', default=False, + help=_("Enable local ARP responder if it is supported")), ] diff --git a/neutron/plugins/openvswitch/common/constants.py b/neutron/plugins/openvswitch/common/constants.py index 3a5b4aaae93..f6e748b53b3 100644 --- a/neutron/plugins/openvswitch/common/constants.py +++ b/neutron/plugins/openvswitch/common/constants.py @@ -45,7 +45,9 @@ GRE_TUN_TO_LV = 2 VXLAN_TUN_TO_LV = 3 LEARN_FROM_TUN = 10 UCAST_TO_TUN = 20 -FLOOD_TO_TUN = 21 +ARP_RESPONDER = 21 +FLOOD_TO_TUN = 22 + # Map tunnel types to tables number TUN_TABLE = {p_const.TYPE_GRE: GRE_TUN_TO_LV, p_const.TYPE_VXLAN: VXLAN_TUN_TO_LV} diff --git a/neutron/tests/unit/agent/linux/test_ovs_lib.py b/neutron/tests/unit/agent/linux/test_ovs_lib.py index 4b4231790d1..a1daa22a01b 100644 --- a/neutron/tests/unit/agent/linux/test_ovs_lib.py +++ b/neutron/tests/unit/agent/linux/test_ovs_lib.py @@ -915,3 +915,35 @@ class OVS_Lib_Test(base.BaseTestCase): min_kernel_ver = constants.MINIMUM_LINUX_KERNEL_OVS_VXLAN self._check_ovs_vxlan_version(min_vxlan_ver, min_vxlan_ver, min_kernel_ver, expecting_ok=True) + + def test_ofctl_arg_supported(self): + with mock.patch('neutron.common.utils.get_random_string') as utils: + utils.return_value = 'test' + supported = ovs_lib.ofctl_arg_supported(self.root_helper, 'cmd', + ['args']) + self.execute.assert_has_calls([ + mock.call(['ovs-vsctl', self.TO, '--', '--if-exists', 'del-br', + 'br-test-test'], root_helper=self.root_helper), + mock.call(['ovs-vsctl', self.TO, '--', '--may-exist', 'add-br', + 'br-test-test'], root_helper=self.root_helper), + mock.call(['ovs-ofctl', 'cmd', 'br-test-test', 'args'], + root_helper=self.root_helper), + mock.call(['ovs-vsctl', self.TO, '--', '--if-exists', 'del-br', + 'br-test-test'], root_helper=self.root_helper) + ]) + self.assertTrue(supported) + + self.execute.side_effect = Exception + supported = ovs_lib.ofctl_arg_supported(self.root_helper, 'cmd', + ['args']) + self.execute.assert_has_calls([ + mock.call(['ovs-vsctl', self.TO, '--', '--if-exists', 'del-br', + 'br-test-test'], root_helper=self.root_helper), + mock.call(['ovs-vsctl', self.TO, '--', '--may-exist', 'add-br', + 'br-test-test'], root_helper=self.root_helper), + mock.call(['ovs-ofctl', 'cmd', 'br-test-test', 'args'], + root_helper=self.root_helper), + mock.call(['ovs-vsctl', self.TO, '--', '--if-exists', 'del-br', + 'br-test-test'], root_helper=self.root_helper) + ]) + self.assertFalse(supported) diff --git a/neutron/tests/unit/openvswitch/test_ovs_defaults.py b/neutron/tests/unit/openvswitch/test_ovs_defaults.py index b2d30111df7..0d5c00f730d 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_defaults.py +++ b/neutron/tests/unit/openvswitch/test_ovs_defaults.py @@ -31,3 +31,5 @@ class ConfigurationTest(base.BaseTestCase): self.assertEqual(0, len(cfg.CONF.OVS.bridge_mappings)) self.assertEqual(0, len(cfg.CONF.OVS.network_vlan_ranges)) self.assertEqual(0, len(cfg.CONF.OVS.tunnel_id_ranges)) + self.assertFalse(cfg.CONF.AGENT.l2_population) + self.assertFalse(cfg.CONF.AGENT.arp_responder) diff --git a/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py b/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py index 6df77160ea9..b219ad5aa43 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py @@ -18,6 +18,7 @@ import contextlib import sys import mock +import netaddr from oslo.config import cfg import testtools @@ -35,6 +36,10 @@ NOTIFIER = ('neutron.plugins.openvswitch.' 'ovs_neutron_plugin.AgentNotifierApi') OVS_LINUX_KERN_VERS_WITHOUT_VXLAN = "3.12.0" +FAKE_MAC = '00:11:22:33:44:55' +FAKE_IP1 = '10.0.0.1' +FAKE_IP2 = '10.0.0.2' + class CreateAgentConfigMap(base.BaseTestCase): @@ -118,7 +123,10 @@ class TestOvsNeutronAgent(base.BaseTestCase): return_value='00:00:00:00:00:01'), mock.patch('neutron.openstack.common.loopingcall.' 'FixedIntervalLoopingCall', - new=MockFixedIntervalLoopingCall)): + new=MockFixedIntervalLoopingCall), + mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.' + 'OVSNeutronAgent._check_arp_responder_support', + return_value=True)): self.agent = ovs_neutron_agent.OVSNeutronAgent(**kwargs) self.agent.tun_br = mock.Mock() self.agent.sg_agent = mock.Mock() @@ -503,6 +511,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): self.agent.local_vlan_map = {'net1': lvm1, 'net2': lvm2} self.agent.tun_br_ofports = {'gre': {'1.1.1.1': '1', '2.2.2.2': '2'}} + self.agent.arp_responder_enabled = True def test_fdb_ignore_network(self): self._prepare_l2_pop_ofports() @@ -528,7 +537,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): 'segment_id': 'tun2', 'ports': {'agent_ip': - [['mac', 'ip'], + [[FAKE_MAC, FAKE_IP1], n_const.FLOODING_ENTRY]}}} with mock.patch.object(self.agent.tun_br, "defer_apply_on") as defer_fn: @@ -545,7 +554,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): 'segment_id': 'tun1', 'ports': {'2.2.2.2': - [['mac', 'ip'], + [[FAKE_MAC, FAKE_IP1], n_const.FLOODING_ENTRY]}}} with contextlib.nested( mock.patch.object(self.agent.tun_br, 'add_flow'), @@ -554,12 +563,30 @@ class TestOvsNeutronAgent(base.BaseTestCase): ) as (add_flow_fn, mod_flow_fn, add_tun_fn): self.agent.fdb_add(None, fdb_entry) self.assertFalse(add_tun_fn.called) - add_flow_fn.assert_called_with(table=constants.UCAST_TO_TUN, - priority=2, - dl_vlan='vlan1', - dl_dst='mac', - actions='strip_vlan,' - 'set_tunnel:seg1,output:2') + actions = ('move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],' + 'mod_dl_src:%(mac)s,' + 'load:0x2->NXM_OF_ARP_OP[],' + 'move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],' + 'move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],' + 'load:%(mac)#x->NXM_NX_ARP_SHA[],' + 'load:%(ip)#x->NXM_OF_ARP_SPA[],' + 'in_port' % + {'mac': netaddr.EUI(FAKE_MAC, dialect=netaddr.mac_unix), + 'ip': netaddr.IPAddress(FAKE_IP1)}) + add_flow_fn.assert_has_calls([ + mock.call(table=constants.ARP_RESPONDER, + priority=1, + proto='arp', + dl_vlan='vlan1', + nw_dst=FAKE_IP1, + actions=actions), + mock.call(table=constants.UCAST_TO_TUN, + priority=2, + dl_vlan='vlan1', + dl_dst=FAKE_MAC, + actions='strip_vlan,' + 'set_tunnel:seg1,output:2') + ]) mod_flow_fn.assert_called_with(table=constants.FLOOD_TO_TUN, dl_vlan='vlan1', actions='strip_vlan,' @@ -572,16 +599,22 @@ class TestOvsNeutronAgent(base.BaseTestCase): 'segment_id': 'tun2', 'ports': {'2.2.2.2': - [['mac', 'ip'], + [[FAKE_MAC, FAKE_IP1], n_const.FLOODING_ENTRY]}}} with contextlib.nested( mock.patch.object(self.agent.tun_br, 'mod_flow'), mock.patch.object(self.agent.tun_br, 'delete_flows'), ) as (mod_flow_fn, del_flow_fn): self.agent.fdb_remove(None, fdb_entry) - del_flow_fn.assert_called_with(table=constants.UCAST_TO_TUN, - dl_vlan='vlan2', - dl_dst='mac') + del_flow_fn.assert_has_calls([ + mock.call(table=constants.ARP_RESPONDER, + proto='arp', + dl_vlan='vlan2', + nw_dst=FAKE_IP1), + mock.call(table=constants.UCAST_TO_TUN, + dl_vlan='vlan2', + dl_dst=FAKE_MAC) + ]) mod_flow_fn.assert_called_with(table=constants.FLOOD_TO_TUN, dl_vlan='vlan2', actions='strip_vlan,' @@ -592,7 +625,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): fdb_entry = {'net1': {'network_type': 'gre', 'segment_id': 'tun1', - 'ports': {'1.1.1.1': [['mac', 'ip']]}}} + 'ports': {'1.1.1.1': [[FAKE_MAC, FAKE_IP1]]}}} with contextlib.nested( mock.patch.object(self.agent.tun_br, 'add_flow'), mock.patch.object(self.agent.tun_br, 'mod_flow'), @@ -600,7 +633,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): ) as (add_flow_fn, mod_flow_fn, add_tun_fn): self.agent.fdb_add(None, fdb_entry) self.assertFalse(add_tun_fn.called) - fdb_entry['net1']['ports']['10.10.10.10'] = [['mac', 'ip']] + fdb_entry['net1']['ports']['10.10.10.10'] = [[FAKE_MAC, FAKE_IP1]] self.agent.fdb_add(None, fdb_entry) add_tun_fn.assert_called_with('gre-0a0a0a0a', '10.10.10.10', 'gre') @@ -617,6 +650,39 @@ class TestOvsNeutronAgent(base.BaseTestCase): self.agent.fdb_remove(None, fdb_entry) del_port_fn.assert_called_once_with('gre-02020202') + def test_fdb_update_chg_ip(self): + self._prepare_l2_pop_ofports() + fdb_entries = {'chg_ip': + {'net1': + {'agent_ip': + {'before': [[FAKE_MAC, FAKE_IP1]], + 'after': [[FAKE_MAC, FAKE_IP2]]}}}} + with contextlib.nested( + mock.patch.object(self.agent.tun_br, 'add_flow'), + mock.patch.object(self.agent.tun_br, 'delete_flows') + ) as (add_flow_fn, del_flow_fn): + self.agent.fdb_update(None, fdb_entries) + actions = ('move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],' + 'mod_dl_src:%(mac)s,' + 'load:0x2->NXM_OF_ARP_OP[],' + 'move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],' + 'move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],' + 'load:%(mac)#x->NXM_NX_ARP_SHA[],' + 'load:%(ip)#x->NXM_OF_ARP_SPA[],' + 'in_port' % + {'mac': netaddr.EUI(FAKE_MAC, dialect=netaddr.mac_unix), + 'ip': netaddr.IPAddress(FAKE_IP2)}) + add_flow_fn.assert_called_once_with(table=constants.ARP_RESPONDER, + priority=1, + proto='arp', + dl_vlan='vlan1', + nw_dst=FAKE_IP2, + actions=actions) + del_flow_fn.assert_called_once_with(table=constants.ARP_RESPONDER, + proto='arp', + dl_vlan='vlan1', + nw_dst=FAKE_IP1) + def test_recl_lv_port_to_preserve(self): self._prepare_l2_pop_ofports() self.agent.l2_pop = True @@ -772,7 +838,10 @@ class AncillaryBridgesTest(base.BaseTestCase): return_value=bridges), mock.patch( 'neutron.agent.linux.ovs_lib.get_bridge_external_bridge_id', - side_effect=pullup_side_effect)): + side_effect=pullup_side_effect), + mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.' + 'OVSNeutronAgent._check_arp_responder_support', + return_value=True)): self.agent = ovs_neutron_agent.OVSNeutronAgent(**self.kwargs) self.assertEqual(len(ancillary), len(self.agent.ancillary_brs)) if ancillary: diff --git a/neutron/tests/unit/openvswitch/test_ovs_tunnel.py b/neutron/tests/unit/openvswitch/test_ovs_tunnel.py index 1940730fad1..8ba75085cbd 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_tunnel.py +++ b/neutron/tests/unit/openvswitch/test_ovs_tunnel.py @@ -73,6 +73,12 @@ class TunnelTest(base.BaseTestCase): 'neutron.openstack.common.rpc.impl_fake') cfg.CONF.set_override('report_interval', 0, 'AGENT') + check_arp_responder_str = ('neutron.plugins.openvswitch.agent.' + 'ovs_neutron_agent.OVSNeutronAgent.' + '_check_arp_responder_support') + self.mock_check_arp_resp = mock.patch(check_arp_responder_str).start() + self.mock_check_arp_resp.return_value = True + self.INT_BRIDGE = 'integration_bridge' self.TUN_BRIDGE = 'tunnel_bridge' self.MAP_TUN_BRIDGE = 'tunnel_bridge_mapping' @@ -148,12 +154,12 @@ class TunnelTest(base.BaseTestCase): in_port=self.INT_OFPORT, actions="resubmit(,%s)" % constants.PATCH_LV_TO_TUN), - mock.call.add_flow(priority=0, actions='drop'), - mock.call.add_flow(table=constants.PATCH_LV_TO_TUN, + mock.call.add_flow(priority=0, actions="drop"), + mock.call.add_flow(priority=0, table=constants.PATCH_LV_TO_TUN, dl_dst=UCAST_MAC, actions="resubmit(,%s)" % constants.UCAST_TO_TUN), - mock.call.add_flow(table=constants.PATCH_LV_TO_TUN, + mock.call.add_flow(priority=0, table=constants.PATCH_LV_TO_TUN, dl_dst=BCAST_MAC, actions="resubmit(,%s)" % constants.FLOOD_TO_TUN), @@ -247,6 +253,41 @@ class TunnelTest(base.BaseTestCase): self.VETH_MTU) self._verify_mock_calls() + # TODO(ethuleau): Initially, local ARP responder is be dependent to the + # ML2 l2 population mechanism driver. + # The next two tests use l2_pop flag to test ARP responder + def test_construct_with_arp_responder(self): + ovs_neutron_agent.OVSNeutronAgent(self.INT_BRIDGE, + self.TUN_BRIDGE, + '10.0.0.1', self.NET_MAPPING, + 'sudo', 2, ['gre'], + self.VETH_MTU, l2_population=True, + arp_responder=True) + self.mock_tun_bridge_expected.insert( + 5, mock.call.add_flow(table=constants.PATCH_LV_TO_TUN, + priority=1, + proto="arp", + dl_dst="ff:ff:ff:ff:ff:ff", + actions="resubmit(,%s)" % + constants.ARP_RESPONDER) + ) + self.mock_tun_bridge_expected.insert( + 12, mock.call.add_flow(table=constants.ARP_RESPONDER, + priority=0, + actions="resubmit(,%s)" % + constants.FLOOD_TO_TUN) + ) + self._verify_mock_calls() + + def test_construct_without_arp_responder(self): + ovs_neutron_agent.OVSNeutronAgent(self.INT_BRIDGE, + self.TUN_BRIDGE, + '10.0.0.1', self.NET_MAPPING, + 'sudo', 2, ['gre'], + self.VETH_MTU, l2_population=False, + arp_responder=True) + self._verify_mock_calls() + def test_construct_vxlan(self): with mock.patch.object(ovs_lib, 'get_installed_ovs_klm_version', return_value="1.10") as klm_ver: