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:
parent
f19c92b057
commit
befa0b9184
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
41
neutron/plugins/ml2/drivers/l2pop/README
Normal file
41
neutron/plugins/ml2/drivers/l2pop/README
Normal 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
|
@ -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
|
||||
|
@ -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")),
|
||||
]
|
||||
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
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='mac',
|
||||
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,
|
||||
del_flow_fn.assert_has_calls([
|
||||
mock.call(table=constants.ARP_RESPONDER,
|
||||
proto='arp',
|
||||
dl_vlan='vlan2',
|
||||
dl_dst='mac')
|
||||
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:
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user