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:
parent
3e8557d4a9
commit
98a9308728
@ -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
|
||||
|
@ -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,10 +164,16 @@ class OVSBridge:
|
||||
|
||||
def add_flow(self, **kwargs):
|
||||
flow_str = self.add_or_mod_flow_str(**kwargs)
|
||||
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)
|
||||
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):
|
||||
@ -173,12 +182,32 @@ class OVSBridge:
|
||||
if "actions" in kwargs:
|
||||
flow_expr_arr.append("actions=%s" % (kwargs["actions"]))
|
||||
flow_str = ",".join(flow_expr_arr)
|
||||
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
|
||||
|
@ -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]
|
||||
|
@ -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,8 +323,95 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
|
||||
if tunnel_ip == self.local_ip:
|
||||
return
|
||||
tun_name = '%s-%s' % (tunnel_type, tunnel_id)
|
||||
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])
|
||||
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))
|
||||
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)
|
||||
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,6 +1007,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
|
||||
details = self.plugin_rpc.tunnel_sync(self.context,
|
||||
self.local_ip,
|
||||
tunnel_type)
|
||||
if not self.l2_pop:
|
||||
tunnels = details['tunnels']
|
||||
for tunnel in tunnels:
|
||||
if self.local_ip != tunnel['ip_address']:
|
||||
@ -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
|
||||
|
@ -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")),
|
||||
]
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user