diff --git a/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini b/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini index 48442db521..9a1b948d2d 100644 --- a/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini +++ b/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini @@ -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 diff --git a/neutron/agent/linux/ovs_lib.py b/neutron/agent/linux/ovs_lib.py index 2c2bd9d8b5..faa335ea6a 100644 --- a/neutron/agent/linux/ovs_lib.py +++ b/neutron/agent/linux/ovs_lib.py @@ -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 diff --git a/neutron/plugins/ml2/drivers/l2pop/constants.py b/neutron/plugins/ml2/drivers/l2pop/constants.py index 74ca3a1ab9..85ed7a5e4e 100644 --- a/neutron/plugins/ml2/drivers/l2pop/constants.py +++ b/neutron/plugins/ml2/drivers/l2pop/constants.py @@ -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] diff --git a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py index 5a5182dc7f..eefe384367 100644 --- a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py @@ -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 diff --git a/neutron/plugins/openvswitch/common/config.py b/neutron/plugins/openvswitch/common/config.py index 76e522f4fb..1aab164d8e 100644 --- a/neutron/plugins/openvswitch/common/config.py +++ b/neutron/plugins/openvswitch/common/config.py @@ -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")), ] diff --git a/neutron/tests/unit/openvswitch/test_ovs_lib.py b/neutron/tests/unit/openvswitch/test_ovs_lib.py index 1b9486affe..ab9ff8fe4f 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_lib.py +++ b/neutron/tests/unit/openvswitch/test_ovs_lib.py @@ -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) diff --git a/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py b/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py index 3cdd9d9eef..e74fc31495 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py @@ -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): diff --git a/neutron/tests/unit/openvswitch/test_ovs_tunnel.py b/neutron/tests/unit/openvswitch/test_ovs_tunnel.py index e7196b358c..d5bd080929 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_tunnel.py +++ b/neutron/tests/unit/openvswitch/test_ovs_tunnel.py @@ -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()