Merge "Implement local ARP responder onto OVS agent"
This commit is contained in:
@@ -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 addresses 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,
|
||||
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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user