Implement local ARP responder onto OVS agent

With ML2 plugin and l2-pop mechanism driver, it's possible to locally
answer to the ARP request of the VM and avoid ARP broadcasting emulation
on overlay which is costly.

When this functionality is enabled, the OVS flows logic evolves to [1].
This functionality was introduce in 2.1 OVS branch [2].

A README is added to describe l2-pop mechanism driver and the agents
particularities.

[1] https://wiki.openstack.org/wiki/Ovs-flow-logic#OVS_flows_logic_with_local_ARP_responder
[2] http://git.openvswitch.org/cgi-bin/gitweb.cgi?p=openvswitch;a=commitdiff;h=f6c8a6b163af343c66aea54953553d84863835f7

DocImpact: New OVS agent flag 'arp_responder' set to false by default
Closes-Bug: #1237427
Change-Id: Ic28610faf2df6566d8d876fcd7aed333647970e2
This commit is contained in:
Édouard Thuleau 2014-01-06 13:58:39 +01:00
parent f19c92b057
commit befa0b9184
11 changed files with 386 additions and 24 deletions

View File

@ -128,6 +128,11 @@
# #
# l2_population = False # 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] [securitygroup]
# Firewall driver for realizing neutron security group function. # Firewall driver for realizing neutron security group function.
# firewall_driver = neutron.agent.firewall.NoopFirewallDriver # firewall_driver = neutron.agent.firewall.NoopFirewallDriver

View File

@ -22,6 +22,7 @@ from oslo.config import cfg
from neutron.agent.linux import ip_lib from neutron.agent.linux import ip_lib
from neutron.agent.linux import utils from neutron.agent.linux import utils
from neutron.common import exceptions from neutron.common import exceptions
from neutron.common import utils as common_utils
from neutron.openstack.common import excutils from neutron.openstack.common import excutils
from neutron.openstack.common import jsonutils from neutron.openstack.common import jsonutils
from neutron.openstack.common import log as logging 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) flow_expr_arr.append(actions)
return ','.join(flow_expr_arr) 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

View File

@ -18,8 +18,11 @@
"""Utilities and helper functions.""" """Utilities and helper functions."""
import datetime
import hashlib
import logging as std_logging import logging as std_logging
import os import os
import random
import signal import signal
import socket import socket
@ -199,3 +202,17 @@ def log_opt_values(log):
def is_valid_vlan_tag(vlan): def is_valid_vlan_tag(vlan):
return q_const.MIN_VLAN_TAG <= vlan <= q_const.MAX_VLAN_TAG 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]

View File

@ -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

View File

@ -152,7 +152,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
veth_mtu=None, l2_population=False, veth_mtu=None, l2_population=False,
minimize_polling=False, minimize_polling=False,
ovsdb_monitor_respawn_interval=( ovsdb_monitor_respawn_interval=(
constants.DEFAULT_OVSDBMON_RESPAWN)): constants.DEFAULT_OVSDBMON_RESPAWN),
arp_responder=False):
'''Constructor. '''Constructor.
:param integ_br: name of the integration bridge. :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 :param ovsdb_monitor_respawn_interval: Optional, when using polling
minimization, the number of seconds to wait before respawning minimization, the number of seconds to wait before respawning
the ovsdb monitor. the ovsdb monitor.
:param arp_responder: Optional, enable local ARP responder if it is
supported.
''' '''
self.veth_mtu = veth_mtu self.veth_mtu = veth_mtu
self.root_helper = root_helper self.root_helper = root_helper
@ -177,6 +180,11 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
q_const.MAX_VLAN_TAG)) q_const.MAX_VLAN_TAG))
self.tunnel_types = tunnel_types or [] self.tunnel_types = tunnel_types or []
self.l2_pop = l2_population 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 = { self.agent_state = {
'binary': 'neutron-openvswitch-agent', 'binary': 'neutron-openvswitch-agent',
'host': cfg.CONF.host, 'host': cfg.CONF.host,
@ -184,7 +192,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
'configurations': {'bridge_mappings': bridge_mappings, 'configurations': {'bridge_mappings': bridge_mappings,
'tunnel_types': self.tunnel_types, 'tunnel_types': self.tunnel_types,
'tunneling_ip': local_ip, '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, 'agent_type': q_const.AGENT_TYPE_OVS,
'start_flag': True} 'start_flag': True}
@ -233,6 +243,20 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
LOG.exception(_("Agent terminated")) LOG.exception(_("Agent terminated"))
raise SystemExit(1) 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): def _report_state(self):
# How many devices are likely used by a VM # How many devices are likely used by a VM
self.agent_state.get('configurations')['devices'] = ( self.agent_state.get('configurations')['devices'] = (
@ -375,7 +399,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
actions="strip_vlan,set_tunnel:%s," actions="strip_vlan,set_tunnel:%s,"
"output:%s" % (lvm.segmentation_id, ofports)) "output:%s" % (lvm.segmentation_id, ofports))
else: 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, self.tun_br.add_flow(table=constants.UCAST_TO_TUN,
priority=2, priority=2,
dl_vlan=lvm.vlan, dl_vlan=lvm.vlan,
@ -400,11 +425,53 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
# Check if this tunnel port is still used # Check if this tunnel port is still used
self.cleanup_tunnel_port(ofport, lvm.network_type) self.cleanup_tunnel_port(ofport, lvm.network_type)
else: 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, self.tun_br.delete_flows(table=constants.UCAST_TO_TUN,
dl_vlan=lvm.vlan, dl_vlan=lvm.vlan,
dl_dst=port_info[0]) 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): def fdb_update(self, context, fdb_entries):
LOG.debug(_("fdb_update received")) LOG.debug(_("fdb_update received"))
for action, values in fdb_entries.items(): for action, values in fdb_entries.items():
@ -414,6 +481,47 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
getattr(self, method)(context, values) 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): def create_rpc_dispatcher(self):
'''Get the rpc dispatcher for this manager. '''Get the rpc dispatcher for this manager.
@ -701,13 +809,24 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
actions="resubmit(,%s)" % actions="resubmit(,%s)" %
constants.PATCH_LV_TO_TUN) constants.PATCH_LV_TO_TUN)
self.tun_br.add_flow(priority=0, actions="drop") 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 # PATCH_LV_TO_TUN table will handle packets coming from patch_int
# unicasts go to table UCAST_TO_TUN where remote adresses are learnt # unicasts go to table UCAST_TO_TUN where remote adresses are learnt
self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN, 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", dl_dst="00:00:00:00:00:00/01:00:00:00:00:00",
actions="resubmit(,%s)" % constants.UCAST_TO_TUN) actions="resubmit(,%s)" % constants.UCAST_TO_TUN)
# Broadcasts/multicasts go to table FLOOD_TO_TUN that handles flooding # Broadcasts/multicasts go to table FLOOD_TO_TUN that handles flooding
self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN, 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", dl_dst="01:00:00:00:00:00/01:00:00:00:00:00",
actions="resubmit(,%s)" % constants.FLOOD_TO_TUN) actions="resubmit(,%s)" % constants.FLOOD_TO_TUN)
# Tables [tunnel_type]_TUN_TO_LV will set lvid depending on tun_id # Tables [tunnel_type]_TUN_TO_LV will set lvid depending on tun_id
@ -742,6 +861,13 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
priority=0, priority=0,
actions="resubmit(,%s)" % actions="resubmit(,%s)" %
constants.FLOOD_TO_TUN) 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, # FLOOD_TO_TUN will handle flooding in tunnels based on lvid,
# for now, add a default drop action # for now, add a default drop action
self.tun_br.add_flow(table=constants.FLOOD_TO_TUN, 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, tunnel_types=config.AGENT.tunnel_types,
veth_mtu=config.AGENT.veth_mtu, veth_mtu=config.AGENT.veth_mtu,
l2_population=config.AGENT.l2_population, l2_population=config.AGENT.l2_population,
arp_responder=config.AGENT.arp_responder,
) )
# If enable_tunneling is TRUE, set tunnel_type to default to GRE # If enable_tunneling is TRUE, set tunnel_type to default to GRE

View File

@ -80,6 +80,8 @@ agent_opts = [
cfg.BoolOpt('l2_population', default=False, cfg.BoolOpt('l2_population', default=False,
help=_("Use ml2 l2population mechanism driver to learn " help=_("Use ml2 l2population mechanism driver to learn "
"remote mac and IPs and improve tunnel scalability")), "remote mac and IPs and improve tunnel scalability")),
cfg.BoolOpt('arp_responder', default=False,
help=_("Enable local ARP responder if it is supported")),
] ]

View File

@ -45,7 +45,9 @@ GRE_TUN_TO_LV = 2
VXLAN_TUN_TO_LV = 3 VXLAN_TUN_TO_LV = 3
LEARN_FROM_TUN = 10 LEARN_FROM_TUN = 10
UCAST_TO_TUN = 20 UCAST_TO_TUN = 20
FLOOD_TO_TUN = 21 ARP_RESPONDER = 21
FLOOD_TO_TUN = 22
# Map tunnel types to tables number # Map tunnel types to tables number
TUN_TABLE = {p_const.TYPE_GRE: GRE_TUN_TO_LV, TUN_TABLE = {p_const.TYPE_GRE: GRE_TUN_TO_LV,
p_const.TYPE_VXLAN: VXLAN_TUN_TO_LV} p_const.TYPE_VXLAN: VXLAN_TUN_TO_LV}

View File

@ -915,3 +915,35 @@ class OVS_Lib_Test(base.BaseTestCase):
min_kernel_ver = constants.MINIMUM_LINUX_KERNEL_OVS_VXLAN min_kernel_ver = constants.MINIMUM_LINUX_KERNEL_OVS_VXLAN
self._check_ovs_vxlan_version(min_vxlan_ver, min_vxlan_ver, self._check_ovs_vxlan_version(min_vxlan_ver, min_vxlan_ver,
min_kernel_ver, expecting_ok=True) 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)

View File

@ -31,3 +31,5 @@ class ConfigurationTest(base.BaseTestCase):
self.assertEqual(0, len(cfg.CONF.OVS.bridge_mappings)) 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.network_vlan_ranges))
self.assertEqual(0, len(cfg.CONF.OVS.tunnel_id_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)

View File

@ -18,6 +18,7 @@ import contextlib
import sys import sys
import mock import mock
import netaddr
from oslo.config import cfg from oslo.config import cfg
import testtools import testtools
@ -35,6 +36,10 @@ NOTIFIER = ('neutron.plugins.openvswitch.'
'ovs_neutron_plugin.AgentNotifierApi') 'ovs_neutron_plugin.AgentNotifierApi')
OVS_LINUX_KERN_VERS_WITHOUT_VXLAN = "3.12.0" 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): class CreateAgentConfigMap(base.BaseTestCase):
@ -118,7 +123,10 @@ class TestOvsNeutronAgent(base.BaseTestCase):
return_value='00:00:00:00:00:01'), return_value='00:00:00:00:00:01'),
mock.patch('neutron.openstack.common.loopingcall.' mock.patch('neutron.openstack.common.loopingcall.'
'FixedIntervalLoopingCall', '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 = ovs_neutron_agent.OVSNeutronAgent(**kwargs)
self.agent.tun_br = mock.Mock() self.agent.tun_br = mock.Mock()
self.agent.sg_agent = 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.local_vlan_map = {'net1': lvm1, 'net2': lvm2}
self.agent.tun_br_ofports = {'gre': self.agent.tun_br_ofports = {'gre':
{'1.1.1.1': '1', '2.2.2.2': '2'}} {'1.1.1.1': '1', '2.2.2.2': '2'}}
self.agent.arp_responder_enabled = True
def test_fdb_ignore_network(self): def test_fdb_ignore_network(self):
self._prepare_l2_pop_ofports() self._prepare_l2_pop_ofports()
@ -528,7 +537,7 @@ class TestOvsNeutronAgent(base.BaseTestCase):
'segment_id': 'tun2', 'segment_id': 'tun2',
'ports': 'ports':
{'agent_ip': {'agent_ip':
[['mac', 'ip'], [[FAKE_MAC, FAKE_IP1],
n_const.FLOODING_ENTRY]}}} n_const.FLOODING_ENTRY]}}}
with mock.patch.object(self.agent.tun_br, with mock.patch.object(self.agent.tun_br,
"defer_apply_on") as defer_fn: "defer_apply_on") as defer_fn:
@ -545,7 +554,7 @@ class TestOvsNeutronAgent(base.BaseTestCase):
'segment_id': 'tun1', 'segment_id': 'tun1',
'ports': 'ports':
{'2.2.2.2': {'2.2.2.2':
[['mac', 'ip'], [[FAKE_MAC, FAKE_IP1],
n_const.FLOODING_ENTRY]}}} n_const.FLOODING_ENTRY]}}}
with contextlib.nested( with contextlib.nested(
mock.patch.object(self.agent.tun_br, 'add_flow'), 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): ) as (add_flow_fn, mod_flow_fn, add_tun_fn):
self.agent.fdb_add(None, fdb_entry) self.agent.fdb_add(None, fdb_entry)
self.assertFalse(add_tun_fn.called) self.assertFalse(add_tun_fn.called)
add_flow_fn.assert_called_with(table=constants.UCAST_TO_TUN, actions = ('move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],'
priority=2, 'mod_dl_src:%(mac)s,'
dl_vlan='vlan1', 'load:0x2->NXM_OF_ARP_OP[],'
dl_dst='mac', 'move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],'
actions='strip_vlan,' 'move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],'
'set_tunnel:seg1,output:2') '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, mod_flow_fn.assert_called_with(table=constants.FLOOD_TO_TUN,
dl_vlan='vlan1', dl_vlan='vlan1',
actions='strip_vlan,' actions='strip_vlan,'
@ -572,16 +599,22 @@ class TestOvsNeutronAgent(base.BaseTestCase):
'segment_id': 'tun2', 'segment_id': 'tun2',
'ports': 'ports':
{'2.2.2.2': {'2.2.2.2':
[['mac', 'ip'], [[FAKE_MAC, FAKE_IP1],
n_const.FLOODING_ENTRY]}}} n_const.FLOODING_ENTRY]}}}
with contextlib.nested( with contextlib.nested(
mock.patch.object(self.agent.tun_br, 'mod_flow'), mock.patch.object(self.agent.tun_br, 'mod_flow'),
mock.patch.object(self.agent.tun_br, 'delete_flows'), mock.patch.object(self.agent.tun_br, 'delete_flows'),
) as (mod_flow_fn, del_flow_fn): ) as (mod_flow_fn, del_flow_fn):
self.agent.fdb_remove(None, fdb_entry) self.agent.fdb_remove(None, fdb_entry)
del_flow_fn.assert_called_with(table=constants.UCAST_TO_TUN, del_flow_fn.assert_has_calls([
dl_vlan='vlan2', mock.call(table=constants.ARP_RESPONDER,
dl_dst='mac') 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, mod_flow_fn.assert_called_with(table=constants.FLOOD_TO_TUN,
dl_vlan='vlan2', dl_vlan='vlan2',
actions='strip_vlan,' actions='strip_vlan,'
@ -592,7 +625,7 @@ class TestOvsNeutronAgent(base.BaseTestCase):
fdb_entry = {'net1': fdb_entry = {'net1':
{'network_type': 'gre', {'network_type': 'gre',
'segment_id': 'tun1', 'segment_id': 'tun1',
'ports': {'1.1.1.1': [['mac', 'ip']]}}} 'ports': {'1.1.1.1': [[FAKE_MAC, FAKE_IP1]]}}}
with contextlib.nested( with contextlib.nested(
mock.patch.object(self.agent.tun_br, 'add_flow'), mock.patch.object(self.agent.tun_br, 'add_flow'),
mock.patch.object(self.agent.tun_br, 'mod_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): ) as (add_flow_fn, mod_flow_fn, add_tun_fn):
self.agent.fdb_add(None, fdb_entry) self.agent.fdb_add(None, fdb_entry)
self.assertFalse(add_tun_fn.called) 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) self.agent.fdb_add(None, fdb_entry)
add_tun_fn.assert_called_with('gre-0a0a0a0a', '10.10.10.10', 'gre') 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) self.agent.fdb_remove(None, fdb_entry)
del_port_fn.assert_called_once_with('gre-02020202') 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): def test_recl_lv_port_to_preserve(self):
self._prepare_l2_pop_ofports() self._prepare_l2_pop_ofports()
self.agent.l2_pop = True self.agent.l2_pop = True
@ -772,7 +838,10 @@ class AncillaryBridgesTest(base.BaseTestCase):
return_value=bridges), return_value=bridges),
mock.patch( mock.patch(
'neutron.agent.linux.ovs_lib.get_bridge_external_bridge_id', '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.agent = ovs_neutron_agent.OVSNeutronAgent(**self.kwargs)
self.assertEqual(len(ancillary), len(self.agent.ancillary_brs)) self.assertEqual(len(ancillary), len(self.agent.ancillary_brs))
if ancillary: if ancillary:

View File

@ -73,6 +73,12 @@ class TunnelTest(base.BaseTestCase):
'neutron.openstack.common.rpc.impl_fake') 'neutron.openstack.common.rpc.impl_fake')
cfg.CONF.set_override('report_interval', 0, 'AGENT') 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.INT_BRIDGE = 'integration_bridge'
self.TUN_BRIDGE = 'tunnel_bridge' self.TUN_BRIDGE = 'tunnel_bridge'
self.MAP_TUN_BRIDGE = 'tunnel_bridge_mapping' self.MAP_TUN_BRIDGE = 'tunnel_bridge_mapping'
@ -148,12 +154,12 @@ class TunnelTest(base.BaseTestCase):
in_port=self.INT_OFPORT, in_port=self.INT_OFPORT,
actions="resubmit(,%s)" % actions="resubmit(,%s)" %
constants.PATCH_LV_TO_TUN), constants.PATCH_LV_TO_TUN),
mock.call.add_flow(priority=0, actions='drop'), mock.call.add_flow(priority=0, actions="drop"),
mock.call.add_flow(table=constants.PATCH_LV_TO_TUN, mock.call.add_flow(priority=0, table=constants.PATCH_LV_TO_TUN,
dl_dst=UCAST_MAC, dl_dst=UCAST_MAC,
actions="resubmit(,%s)" % actions="resubmit(,%s)" %
constants.UCAST_TO_TUN), 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, dl_dst=BCAST_MAC,
actions="resubmit(,%s)" % actions="resubmit(,%s)" %
constants.FLOOD_TO_TUN), constants.FLOOD_TO_TUN),
@ -247,6 +253,41 @@ class TunnelTest(base.BaseTestCase):
self.VETH_MTU) self.VETH_MTU)
self._verify_mock_calls() 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): def test_construct_vxlan(self):
with mock.patch.object(ovs_lib, 'get_installed_ovs_klm_version', with mock.patch.object(ovs_lib, 'get_installed_ovs_klm_version',
return_value="1.10") as klm_ver: return_value="1.10") as klm_ver: