OVS agent implementation of l2-population

This patchset implements l2-population RPC callbacks in OVS agents,
it enables plugin to populate forwarding table following portbindings
events.

For now, it doesn't include ARP responder implementation which is
deferred to a future patchset (As this feature isn't yet supported by
OVS, it will require the use of an external responder such as ebtables)

It anyway brings some improvements in tunnelling management, as agent
will tear-down unecessary tunnels, and flood packets on a per-network
basis rather than to all other agents.

These changes should anyway have a limited risk, as tunnel management
won't be affected as long as l2_population option is not set. This
option must be used in conjonction with ml2 plugin using l2population
mechanism driver.

Implements: blueprint l2-population

Change-Id: I5185eefedb0ff392bc8b99d16f810813e26ff58d
This commit is contained in:
Francois Eleouet 2013-08-22 16:51:01 +02:00
parent 3e8557d4a9
commit 98a9308728
8 changed files with 401 additions and 55 deletions

View File

@ -113,6 +113,14 @@
# veth_mtu =
# Example: veth_mtu = 1504
# (BoolOpt) Flag to enable l2-population extension. This option should only be
# used in conjunction with ml2 plugin and l2population mechanism driver. It'll
# enable plugin to populate remote ports macs and IPs (using fdb_add/remove
# RPC calbbacks instead of tunnel_sync/update) on OVS agents in order to
# optimize tunnel management.
#
# l2_population = False
[securitygroup]
# Firewall driver for realizing neutron security group function.
# firewall_driver = neutron.agent.firewall.NoopFirewallDriver

View File

@ -49,6 +49,8 @@ class OVSBridge:
self.br_name = br_name
self.root_helper = root_helper
self.re_id = self.re_compile_id()
self.defer_apply_flows = False
self.deferred_flows = {'add': '', 'mod': '', 'del': ''}
def re_compile_id(self):
external = 'external_ids\s*'
@ -92,10 +94,11 @@ class OVSBridge:
args = ["clear", table_name, record, column]
self.run_vsctl(args)
def run_ofctl(self, cmd, args):
def run_ofctl(self, cmd, args, process_input=None):
full_args = ["ovs-ofctl", cmd, self.br_name] + args
try:
return utils.execute(full_args, root_helper=self.root_helper)
return utils.execute(full_args, root_helper=self.root_helper,
process_input=process_input)
except Exception as e:
LOG.error(_("Unable to execute %(cmd)s. Exception: %(exception)s"),
{'cmd': full_args, 'exception': e})
@ -161,11 +164,17 @@ class OVSBridge:
def add_flow(self, **kwargs):
flow_str = self.add_or_mod_flow_str(**kwargs)
self.run_ofctl("add-flow", [flow_str])
if self.defer_apply_flows:
self.deferred_flows['add'] += flow_str + '\n'
else:
self.run_ofctl("add-flow", [flow_str])
def mod_flow(self, **kwargs):
flow_str = self.add_or_mod_flow_str(**kwargs)
self.run_ofctl("mod-flows", [flow_str])
if self.defer_apply_flows:
self.deferred_flows['mod'] += flow_str + '\n'
else:
self.run_ofctl("mod-flows", [flow_str])
def delete_flows(self, **kwargs):
kwargs['delete'] = True
@ -173,12 +182,32 @@ class OVSBridge:
if "actions" in kwargs:
flow_expr_arr.append("actions=%s" % (kwargs["actions"]))
flow_str = ",".join(flow_expr_arr)
self.run_ofctl("del-flows", [flow_str])
if self.defer_apply_flows:
self.deferred_flows['del'] += flow_str + '\n'
else:
self.run_ofctl("del-flows", [flow_str])
def defer_apply_on(self):
LOG.debug(_('defer_apply_on'))
self.defer_apply_flows = True
def defer_apply_off(self):
LOG.debug(_('defer_apply_off'))
for action, flows in self.deferred_flows.items():
if flows:
LOG.debug(_('Applying following deferred flows '
'to bridge %s'), self.br_name)
for line in flows.splitlines():
LOG.debug(_('%(action)s: %(flow)s'),
{'action': action, 'flow': line})
self.run_ofctl('%s-flows' % action, ['-'], flows)
self.defer_apply_flows = False
self.deferred_flows = {'add': '', 'mod': '', 'del': ''}
def add_tunnel_port(self, port_name, remote_ip, local_ip,
tunnel_type=constants.TYPE_GRE,
vxlan_udp_port=constants.VXLAN_UDP_PORT):
self.run_vsctl(["add-port", self.br_name, port_name])
self.run_vsctl(["--may-exist", "add-port", self.br_name, port_name])
self.set_db_attribute("Interface", port_name, "type", tunnel_type)
if tunnel_type == constants.TYPE_VXLAN:
# Only set the VXLAN UDP port if it's not the default

View File

@ -17,4 +17,6 @@
# @author: Francois Eleouet, Orange
# @author: Mathieu Rohon, Orange
SUPPORTED_AGENT_TYPES = []
from neutron.common import constants
SUPPORTED_AGENT_TYPES = [constants.AGENT_TYPE_OVS]

View File

@ -29,6 +29,7 @@ import time
import eventlet
from oslo.config import cfg
from neutron.agent import l2population_rpc
from neutron.agent.linux import ip_lib
from neutron.agent.linux import ovs_lib
from neutron.agent import rpc as agent_rpc
@ -66,6 +67,8 @@ class LocalVLANMapping:
self.physical_network = physical_network
self.segmentation_id = segmentation_id
self.vif_ports = vif_ports
# set of tunnel ports on which packets should be flooded
self.tun_ofports = set()
def __str__(self):
return ("lv-id = %s type = %s phys-net = %s phys-id = %s" %
@ -116,7 +119,8 @@ class OVSSecurityGroupAgent(sg_rpc.SecurityGroupAgentRpcMixin):
self.init_firewall()
class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
l2population_rpc.L2populationRpcCallBackMixin):
'''Implements OVS-based tunneling, VLANs and flat networks.
Two local bridges are created: an integration bridge (defaults to
@ -151,7 +155,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
def __init__(self, integ_br, tun_br, local_ip,
bridge_mappings, root_helper,
polling_interval, tunnel_types=None,
veth_mtu=None):
veth_mtu=None, l2_population=False):
'''Constructor.
:param integ_br: name of the integration bridge.
@ -170,12 +174,15 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
self.available_local_vlans = set(xrange(q_const.MIN_VLAN_TAG,
q_const.MAX_VLAN_TAG))
self.tunnel_types = tunnel_types or []
self.l2_pop = l2_population
self.agent_state = {
'binary': 'neutron-openvswitch-agent',
'host': cfg.CONF.host,
'topic': q_const.L2_AGENT_TOPIC,
'configurations': {'bridge_mappings': bridge_mappings,
'tunnel_types': self.tunnel_types},
'tunnel_types': self.tunnel_types,
'tunneling_ip': local_ip,
'l2_population': self.l2_pop},
'agent_type': q_const.AGENT_TYPE_OVS,
'start_flag': True}
@ -187,8 +194,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
self.setup_integration_br()
self.setup_physical_bridges(bridge_mappings)
self.local_vlan_map = {}
self.tun_br_ofports = {constants.TYPE_GRE: set(),
constants.TYPE_VXLAN: set()}
self.tun_br_ofports = {constants.TYPE_GRE: {},
constants.TYPE_VXLAN: {}}
self.polling_interval = polling_interval
@ -242,6 +249,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
[topics.NETWORK, topics.DELETE],
[constants.TUNNEL, topics.UPDATE],
[topics.SECURITY_GROUP, topics.UPDATE]]
if self.l2_pop:
consumers.append([topics.L2POPULATION,
topics.UPDATE, cfg.CONF.host])
self.connection = agent_rpc.create_consumers(self.dispatcher,
self.topic,
consumers)
@ -263,7 +273,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
# The network may not be defined on this agent
lvm = self.local_vlan_map.get(network_id)
if lvm:
self.reclaim_local_vlan(network_id, lvm)
self.reclaim_local_vlan(network_id)
else:
LOG.debug(_("Network %s not used on agent."), network_id)
@ -313,7 +323,94 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
if tunnel_ip == self.local_ip:
return
tun_name = '%s-%s' % (tunnel_type, tunnel_id)
self.setup_tunnel_port(tun_name, tunnel_ip, tunnel_type)
if not self.l2_pop:
self.setup_tunnel_port(tun_name, tunnel_ip, tunnel_type)
def fdb_add(self, context, fdb_entries):
LOG.debug(_("fdb_add received"))
for network_id, values in fdb_entries.items():
lvm = self.local_vlan_map.get(network_id)
if not lvm:
# Agent doesn't manage any port in this network
continue
agent_ports = values.get('ports')
agent_ports.pop(self.local_ip, None)
if len(agent_ports):
self.tun_br.defer_apply_on()
for agent_ip, ports in agent_ports.items():
# Ensure we have a tunnel port with this remote agent
ofport = self.tun_br_ofports[
lvm.network_type].get(agent_ip)
if not ofport:
port_name = '%s-%s' % (lvm.network_type, agent_ip)
ofport = self.setup_tunnel_port(port_name, agent_ip,
lvm.network_type)
if ofport == 0:
continue
for port in ports:
self._add_fdb_flow(port, agent_ip, lvm, ofport)
self.tun_br.defer_apply_off()
def fdb_remove(self, context, fdb_entries):
LOG.debug(_("fdb_remove received"))
for network_id, values in fdb_entries.items():
lvm = self.local_vlan_map.get(network_id)
if not lvm:
# Agent doesn't manage any more ports in this network
continue
agent_ports = values.get('ports')
agent_ports.pop(self.local_ip, None)
if len(agent_ports):
self.tun_br.defer_apply_on()
for agent_ip, ports in agent_ports.items():
ofport = self.tun_br_ofports[
lvm.network_type].get(agent_ip)
if not ofport:
continue
for port in ports:
self._del_fdb_flow(port, agent_ip, lvm, ofport)
self.tun_br.defer_apply_off()
def _add_fdb_flow(self, port_info, agent_ip, lvm, ofport):
if port_info == q_const.FLOODING_ENTRY:
lvm.tun_ofports.add(ofport)
ofports = ','.join(lvm.tun_ofports)
self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
priority=1,
dl_vlan=lvm.vlan,
actions="strip_vlan,set_tunnel:%s,"
"output:%s" % (lvm.segmentation_id, ofports))
else:
# TODO(feleouet): add ARP responder entry
self.tun_br.add_flow(table=constants.UCAST_TO_TUN,
priority=2,
dl_vlan=lvm.vlan,
dl_dst=port_info[0],
actions="strip_vlan,set_tunnel:%s,output:%s" %
(lvm.segmentation_id, ofport))
def _del_fdb_flow(self, port_info, agent_ip, lvm, ofport):
if port_info == q_const.FLOODING_ENTRY:
lvm.tun_ofports.remove(ofport)
if len(lvm.tun_ofports) > 0:
ofports = ','.join(lvm.tun_ofports)
self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
priority=1,
dl_vlan=lvm.vlan,
actions="strip_vlan,"
"set_tunnel:%s,output:%s" %
(lvm.segmentation_id, ofports))
else:
# This local vlan doesn't require any more tunelling
self.tun_br.delete_flows(table=constants.FLOOD_TO_TUN,
dl_vlan=lvm.vlan)
# Check if this tunnel port is still used
self.cleanup_tunnel_port(ofport, lvm.network_type)
else:
#TODO(feleouet): remove ARP responder entry
self.tun_br.delete_flows(table=constants.UCAST_TO_TUN,
dl_vlan=lvm.vlan,
dl_dst=port_info[0])
def create_rpc_dispatcher(self):
'''Get the rpc dispatcher for this manager.
@ -348,12 +445,14 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
if network_type in constants.TUNNEL_NETWORK_TYPES:
if self.enable_tunneling:
# outbound broadcast/multicast
ofports = ','.join(self.tun_br_ofports[network_type])
self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
priority=1,
dl_vlan=lvid,
actions="strip_vlan,set_tunnel:%s,"
"output:%s" % (segmentation_id, ofports))
ofports = ','.join(self.tun_br_ofports[network_type].values())
if ofports:
self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
priority=1,
dl_vlan=lvid,
actions="strip_vlan,"
"set_tunnel:%s,output:%s" %
(segmentation_id, ofports))
# inbound from tunnels: set lvid in the right table
# and resubmit to Table LEARN_FROM_TUN for mac learning
self.tun_br.add_flow(table=constants.TUN_TABLE[network_type],
@ -415,13 +514,18 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
{'network_type': network_type,
'net_uuid': net_uuid})
def reclaim_local_vlan(self, net_uuid, lvm):
def reclaim_local_vlan(self, net_uuid):
'''Reclaim a local VLAN.
:param net_uuid: the network uuid associated with this vlan.
:param lvm: a LocalVLANMapping object that tracks (vlan, lsw_id,
vif_ids) mapping.
'''
lvm = self.local_vlan_map.pop(net_uuid, None)
if lvm is None:
LOG.debug(_("Network %s not used on agent."), net_uuid)
return
LOG.info(_("Reclaiming vlan = %(vlan_id)s from net-id = %(net_uuid)s"),
{'vlan_id': lvm.vlan,
'net_uuid': net_uuid})
@ -432,6 +536,10 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
table=constants.TUN_TABLE[lvm.network_type],
tun_id=lvm.segmentation_id)
self.tun_br.delete_flows(dl_vlan=lvm.vlan)
if self.l2_pop:
# Try to remove tunnel ports if not used by other networks
for ofport in lvm.tun_ofports:
self.cleanup_tunnel_port(ofport, lvm.network_type)
elif lvm.network_type == constants.TYPE_FLAT:
if lvm.physical_network in self.phys_brs:
# outbound
@ -463,7 +571,6 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
{'network_type': lvm.network_type,
'net_uuid': net_uuid})
del self.local_vlan_map[net_uuid]
self.available_local_vlans.add(lvm.vlan)
def port_bound(self, port, net_uuid,
@ -509,7 +616,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
lvm.vif_ports.pop(vif_id, None)
if not lvm.vif_ports:
self.reclaim_local_vlan(net_uuid, lvm)
self.reclaim_local_vlan(net_uuid)
def port_dead(self, port):
'''Once a port has no binding, put it on the "dead vlan".
@ -737,16 +844,19 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
if ofport < 0:
LOG.error(_("Failed to set-up %(type)s tunnel port to %(ip)s"),
{'type': tunnel_type, 'ip': remote_ip})
else:
self.tun_br_ofports[tunnel_type].add(ofport)
# Add flow in default table to resubmit to the right
# tunelling table (lvid will be set in the latter)
self.tun_br.add_flow(priority=1,
in_port=ofport,
actions="resubmit(,%s)" %
constants.TUN_TABLE[tunnel_type])
return 0
self.tun_br_ofports[tunnel_type][remote_ip] = ofport
# Add flow in default table to resubmit to the right
# tunelling table (lvid will be set in the latter)
self.tun_br.add_flow(priority=1,
in_port=ofport,
actions="resubmit(,%s)" %
constants.TUN_TABLE[tunnel_type])
ofports = ','.join(self.tun_br_ofports[tunnel_type].values())
if ofports and not self.l2_pop:
# Update flooding flows to include the new tunnel
ofports = ','.join(self.tun_br_ofports[tunnel_type])
for network_id, vlan_mapping in self.local_vlan_map.iteritems():
if vlan_mapping.network_type == tunnel_type:
self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
@ -756,6 +866,20 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
"set_tunnel:%s,output:%s" %
(vlan_mapping.segmentation_id,
ofports))
return ofport
def cleanup_tunnel_port(self, tun_ofport, tunnel_type):
# Check if this tunnel port is still used
for lvm in self.local_vlan_map.values():
if tun_ofport in lvm.tun_ofports:
break
# If not, remove it
else:
for remote_ip, ofport in self.tun_br_ofports[tunnel_type].items():
if ofport == tun_ofport:
port_name = '%s-%s' % (tunnel_type, remote_ip)
self.tun_br.delete_port(port_name)
self.tun_br_ofports[tunnel_type].pop(remote_ip, None)
def treat_devices_added(self, devices):
resync = False
@ -883,14 +1007,15 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
details = self.plugin_rpc.tunnel_sync(self.context,
self.local_ip,
tunnel_type)
tunnels = details['tunnels']
for tunnel in tunnels:
if self.local_ip != tunnel['ip_address']:
tunnel_id = tunnel.get('id', tunnel['ip_address'])
tun_name = '%s-%s' % (tunnel_type, tunnel_id)
self.setup_tunnel_port(tun_name,
tunnel['ip_address'],
tunnel_type)
if not self.l2_pop:
tunnels = details['tunnels']
for tunnel in tunnels:
if self.local_ip != tunnel['ip_address']:
tunnel_id = tunnel.get('id', tunnel['ip_address'])
tun_name = '%s-%s' % (tunnel_type, tunnel_id)
self.setup_tunnel_port(tun_name,
tunnel['ip_address'],
tunnel_type)
except Exception as e:
LOG.debug(_("Unable to sync tunnel IP %(local_ip)s: %(e)s"),
{'local_ip': self.local_ip, 'e': e})
@ -1011,6 +1136,7 @@ def create_agent_config_map(config):
polling_interval=config.AGENT.polling_interval,
tunnel_types=config.AGENT.tunnel_types,
veth_mtu=config.AGENT.veth_mtu,
l2_population=config.AGENT.l2_population,
)
# If enable_tunneling is TRUE, set tunnel_type to default to GRE

View File

@ -69,6 +69,9 @@ agent_opts = [
help=_("The UDP port to use for VXLAN tunnels.")),
cfg.IntOpt('veth_mtu', default=None,
help=_("MTU size of veth interfaces")),
cfg.BoolOpt('l2_population', default=False,
help=_("Use ml2 l2population mechanism driver to learn "
"remote mac and IPs and improve tunnel scalability")),
]

View File

@ -158,8 +158,8 @@ class OVS_Lib_Test(base.BaseTestCase):
def test_count_flows(self):
utils.execute(["ovs-ofctl", "dump-flows", self.BR_NAME],
root_helper=self.root_helper).AndReturn('ignore'
'\nflow-1\n')
root_helper=self.root_helper,
process_input=None).AndReturn('ignore\nflow-1\n')
self.mox.ReplayAll()
# counts the number of flows as total lines of output - 2
@ -183,13 +183,37 @@ class OVS_Lib_Test(base.BaseTestCase):
self.br.delete_flows(dl_vlan=vid)
self.mox.VerifyAll()
def test_defer_apply_flows(self):
self.mox.StubOutWithMock(self.br, 'add_or_mod_flow_str')
self.br.add_or_mod_flow_str(
flow='added_flow_1').AndReturn('added_flow_1')
self.br.add_or_mod_flow_str(
flow='added_flow_2').AndReturn('added_flow_2')
self.mox.StubOutWithMock(self.br, '_build_flow_expr_arr')
self.br._build_flow_expr_arr(delete=True,
flow='deleted_flow_1'
).AndReturn(['deleted_flow_1'])
self.mox.StubOutWithMock(self.br, 'run_ofctl')
self.br.run_ofctl('add-flows', ['-'], 'added_flow_1\nadded_flow_2\n')
self.br.run_ofctl('del-flows', ['-'], 'deleted_flow_1\n')
self.mox.ReplayAll()
self.br.defer_apply_on()
self.br.add_flow(flow='added_flow_1')
self.br.defer_apply_on()
self.br.add_flow(flow='added_flow_2')
self.br.delete_flows(flow='deleted_flow_1')
self.br.defer_apply_off()
self.mox.VerifyAll()
def test_add_tunnel_port(self):
pname = "tap99"
local_ip = "1.1.1.1"
remote_ip = "9.9.9.9"
ofport = "6"
utils.execute(["ovs-vsctl", self.TO, "add-port",
utils.execute(["ovs-vsctl", self.TO, "--may-exist", "add-port",
self.BR_NAME, pname], root_helper=self.root_helper)
utils.execute(["ovs-vsctl", self.TO, "set", "Interface",
pname, "type=gre"], root_helper=self.root_helper)

View File

@ -23,6 +23,7 @@ import testtools
from neutron.agent.linux import ip_lib
from neutron.agent.linux import ovs_lib
from neutron.common import constants as n_const
from neutron.openstack.common.rpc import common as rpc_common
from neutron.plugins.openvswitch.agent import ovs_neutron_agent
from neutron.plugins.openvswitch.common import constants
@ -254,15 +255,18 @@ class TestOvsNeutronAgent(base.BaseTestCase):
)
def test_network_delete(self):
with mock.patch.object(self.agent, "reclaim_local_vlan") as recl_fn:
with contextlib.nested(
mock.patch.object(self.agent, "reclaim_local_vlan"),
mock.patch.object(self.agent.tun_br, "cleanup_tunnel_port")
) as (recl_fn, clean_tun_fn):
self.agent.network_delete("unused_context",
network_id="123")
self.assertFalse(recl_fn.called)
self.agent.local_vlan_map["123"] = "LVM object"
self.agent.network_delete("unused_context",
network_id="123")
recl_fn.assert_called_with("123", self.agent.local_vlan_map["123"])
self.assertFalse(clean_tun_fn.called)
recl_fn.assert_called_with("123")
def test_port_update(self):
with contextlib.nested(
@ -412,6 +416,158 @@ class TestOvsNeutronAgent(base.BaseTestCase):
constants.MINIMUM_OVS_VXLAN_VERSION,
expecting_ok=False)
def _prepare_l2_pop_ofports(self):
lvm1 = mock.Mock()
lvm1.network_type = 'gre'
lvm1.vlan = 'vlan1'
lvm1.segmentation_id = 'seg1'
lvm1.tun_ofports = set(['1'])
lvm2 = mock.Mock()
lvm2.network_type = 'gre'
lvm2.vlan = 'vlan2'
lvm2.segmentation_id = 'seg2'
lvm2.tun_ofports = set(['1', '2'])
self.agent.local_vlan_map = {'net1': lvm1, 'net2': lvm2}
self.agent.tun_br_ofports = {'gre':
{'ip_agent_1': '1', 'ip_agent_2': '2'}}
def test_fdb_ignore_network(self):
self._prepare_l2_pop_ofports()
fdb_entry = {'net3': {}}
with contextlib.nested(
mock.patch.object(self.agent.tun_br, 'add_flow'),
mock.patch.object(self.agent.tun_br, 'delete_flows'),
mock.patch.object(self.agent, 'setup_tunnel_port'),
mock.patch.object(self.agent, 'cleanup_tunnel_port')
) as (add_flow_fn, del_flow_fn, add_tun_fn, clean_tun_fn):
self.agent.fdb_add(None, fdb_entry)
self.assertFalse(add_flow_fn.called)
self.assertFalse(add_tun_fn.called)
self.agent.fdb_remove(None, fdb_entry)
self.assertFalse(del_flow_fn.called)
self.assertFalse(clean_tun_fn.called)
def test_fdb_ignore_self(self):
self._prepare_l2_pop_ofports()
self.agent.local_ip = 'agent_ip'
fdb_entry = {'net2':
{'network_type': 'gre',
'segment_id': 'tun2',
'ports':
{'agent_ip':
[['mac', 'ip'],
n_const.FLOODING_ENTRY]}}}
with mock.patch.object(self.agent.tun_br,
"defer_apply_on") as defer_fn:
self.agent.fdb_add(None, fdb_entry)
self.assertFalse(defer_fn.called)
self.agent.fdb_remove(None, fdb_entry)
self.assertFalse(defer_fn.called)
def test_fdb_add_flows(self):
self._prepare_l2_pop_ofports()
fdb_entry = {'net1':
{'network_type': 'gre',
'segment_id': 'tun1',
'ports':
{'ip_agent_2':
[['mac', 'ip'],
n_const.FLOODING_ENTRY]}}}
with contextlib.nested(
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, 'setup_tunnel_port'),
) as (add_flow_fn, mod_flow_fn, add_tun_fn):
add_tun_fn.return_value = '2'
self.agent.fdb_add(None, fdb_entry)
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')
mod_flow_fn.assert_called_with(table=constants.FLOOD_TO_TUN,
priority=1,
dl_vlan='vlan1',
actions='strip_vlan,'
'set_tunnel:seg1,output:1,2')
def test_fdb_del_flows(self):
self._prepare_l2_pop_ofports()
fdb_entry = {'net2':
{'network_type': 'gre',
'segment_id': 'tun2',
'ports':
{'ip_agent_2':
[['mac', 'ip'],
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')
mod_flow_fn.assert_called_with(table=constants.FLOOD_TO_TUN,
priority=1,
dl_vlan='vlan2',
actions='strip_vlan,'
'set_tunnel:seg2,output:1')
def test_fdb_add_port(self):
self._prepare_l2_pop_ofports()
fdb_entry = {'net1':
{'network_type': 'gre',
'segment_id': 'tun1',
'ports': {'ip_agent_1': [['mac', 'ip']]}}}
with contextlib.nested(
mock.patch.object(self.agent.tun_br, 'add_flow'),
mock.patch.object(self.agent.tun_br, 'mod_flow'),
mock.patch.object(self.agent, 'setup_tunnel_port')
) 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']['ip_agent_3'] = [['mac', 'ip']]
self.agent.fdb_add(None, fdb_entry)
add_tun_fn.assert_called_with('gre-ip_agent_3', 'ip_agent_3',
'gre')
def test_fdb_del_port(self):
self._prepare_l2_pop_ofports()
fdb_entry = {'net2':
{'network_type': 'gre',
'segment_id': 'tun2',
'ports': {'ip_agent_2': [n_const.FLOODING_ENTRY]}}}
with contextlib.nested(
mock.patch.object(self.agent.tun_br, 'delete_flows'),
mock.patch.object(self.agent.tun_br, 'delete_port')
) as (del_flow_fn, del_port_fn):
self.agent.fdb_remove(None, fdb_entry)
del_port_fn.assert_called_once_with('gre-ip_agent_2')
def test_recl_lv_port_to_preserve(self):
self._prepare_l2_pop_ofports()
self.agent.l2_pop = True
self.agent.enable_tunneling = True
with mock.patch.object(
self.agent.tun_br, 'cleanup_tunnel_port'
) as clean_tun_fn:
self.agent.reclaim_local_vlan('net1')
self.assertFalse(clean_tun_fn.called)
def test_recl_lv_port_to_remove(self):
self._prepare_l2_pop_ofports()
self.agent.l2_pop = True
self.agent.enable_tunneling = True
with contextlib.nested(
mock.patch.object(self.agent.tun_br, 'delete_port'),
mock.patch.object(self.agent.tun_br, 'delete_flows')
) as (del_port_fn, del_flow_fn):
self.agent.reclaim_local_vlan('net2')
del_port_fn.assert_called_once_with('gre-ip_agent_2')
class AncillaryBridgesTest(base.BaseTestCase):

View File

@ -44,10 +44,7 @@ LVM_FLAT = ovs_neutron_agent.LocalVLANMapping(
LVM_VLAN = ovs_neutron_agent.LocalVLANMapping(
LV_ID, 'vlan', 'net1', LS_ID, VIF_PORTS)
GRE_OFPORTS = set(['11', '12'])
VXLAN_OFPORTS = set(['13', '14'])
TUN_OFPORTS = {constants.TYPE_GRE: GRE_OFPORTS,
constants.TYPE_VXLAN: VXLAN_OFPORTS}
TUN_OFPORTS = {constants.TYPE_GRE: {'ip1': '11', 'ip2': '12'}}
BCAST_MAC = "01:00:00:00:00:00/01:00:00:00:00:00"
UCAST_MAC = "00:00:00:00:00:00/01:00:00:00:00:00"
@ -198,12 +195,13 @@ class TunnelTest(base.BaseTestCase):
self.mox.VerifyAll()
def testProvisionLocalVlan(self):
ofports = ','.join(TUN_OFPORTS[constants.TYPE_GRE].values())
self.mock_tun_bridge.mod_flow(table=constants.FLOOD_TO_TUN,
priority=1,
dl_vlan=LV_ID,
actions="strip_vlan,"
"set_tunnel:%s,output:%s" %
(LS_ID, ','.join(GRE_OFPORTS)))
(LS_ID, ofports))
self.mock_tun_bridge.add_flow(table=constants.TUN_TABLE['gre'],
priority=1,
@ -302,7 +300,7 @@ class TunnelTest(base.BaseTestCase):
self.VETH_MTU)
a.available_local_vlans = set()
a.local_vlan_map[NET_UUID] = LVM
a.reclaim_local_vlan(NET_UUID, LVM)
a.reclaim_local_vlan(NET_UUID)
self.assertTrue(LVM.vlan in a.available_local_vlans)
self.mox.VerifyAll()
@ -325,7 +323,7 @@ class TunnelTest(base.BaseTestCase):
a.available_local_vlans = set()
a.local_vlan_map[NET_UUID] = LVM_FLAT
a.reclaim_local_vlan(NET_UUID, LVM_FLAT)
a.reclaim_local_vlan(NET_UUID)
self.assertTrue(LVM_FLAT.vlan in a.available_local_vlans)
self.mox.VerifyAll()
@ -348,7 +346,7 @@ class TunnelTest(base.BaseTestCase):
a.available_local_vlans = set()
a.local_vlan_map[NET_UUID] = LVM_VLAN
a.reclaim_local_vlan(NET_UUID, LVM_VLAN)
a.reclaim_local_vlan(NET_UUID)
self.assertTrue(LVM_VLAN.vlan in a.available_local_vlans)
self.mox.VerifyAll()
@ -370,7 +368,7 @@ class TunnelTest(base.BaseTestCase):
def testPortUnbound(self):
self.mox.StubOutWithMock(
ovs_neutron_agent.OVSNeutronAgent, 'reclaim_local_vlan')
ovs_neutron_agent.OVSNeutronAgent.reclaim_local_vlan(NET_UUID, LVM)
ovs_neutron_agent.OVSNeutronAgent.reclaim_local_vlan(NET_UUID)
self.mox.ReplayAll()