From 322fa884eae50893aaa6ac2c4c8c862879d2a0db Mon Sep 17 00:00:00 2001 From: fumihiko kakuma Date: Tue, 15 Apr 2014 11:54:50 +0900 Subject: [PATCH] OFAgent: Share codes of l2-population in OVS agent This is step 1 implementation of OFAgent l2-population. OFAgent partially uses codes in OVS agent on implementation of l2-population. We share these codes adding mixin class for OVS to l2-population rpc. We use a ryu library instead of executing a ovs-vsctl command and on OFAgent l2-population is no longer optional. Also a function of local arp responder will be implemented on step 2. related commit: b6133c35dd587f6b01e8ec12757347b2767713a6 Partially implements: blueprint ofagent-l2pop Change-Id: I99a2adfd380a9fefe34c53e0dabc21d8cf5936cc --- neutron/agent/l2population_rpc.py | 126 ++++++++ .../plugins/ml2/drivers/l2pop/constants.py | 3 +- .../ofagent/agent/ofa_neutron_agent.py | 235 +++++++++------ .../openvswitch/agent/ovs_neutron_agent.py | 82 ++---- .../tests/unit/agent/l2population_rpc_base.py | 140 +++++++++ .../tests/unit/agent/test_l2population_rpc.py | 190 ++++++++++++ .../unit/ofagent/test_ofa_neutron_agent.py | 278 ++++++++++++------ .../openvswitch/test_ovs_neutron_agent.py | 46 +-- 8 files changed, 843 insertions(+), 257 deletions(-) create mode 100644 neutron/tests/unit/agent/l2population_rpc_base.py create mode 100644 neutron/tests/unit/agent/test_l2population_rpc.py diff --git a/neutron/agent/l2population_rpc.py b/neutron/agent/l2population_rpc.py index 80c5a97de..3a8ad309f 100644 --- a/neutron/agent/l2population_rpc.py +++ b/neutron/agent/l2population_rpc.py @@ -22,11 +22,22 @@ import abc from oslo.config import cfg import six +from neutron.common import constants as n_const from neutron.common import log +from neutron.openstack.common import log as logging + +LOG = logging.getLogger(__name__) @six.add_metaclass(abc.ABCMeta) class L2populationRpcCallBackMixin(object): + '''General mixin class of L2-population RPC call back. + + The following methods are called through RPC. + add_fdb_entries(), remove_fdb_entries(), update_fdb_entries() + The following methods are used in a agent as an internal method. + fdb_add(), fdb_remove(), fdb_update() + ''' @log.log def add_fdb_entries(self, context, fdb_entries, host=None): @@ -54,3 +65,118 @@ class L2populationRpcCallBackMixin(object): @abc.abstractmethod def fdb_update(self, context, fdb_entries): pass + + +class L2populationRpcCallBackTunnelMixin(L2populationRpcCallBackMixin): + '''Mixin class of L2-population call back for Tunnel. + + The following all methods are used in a agent as an internal method. + ''' + + @abc.abstractmethod + def add_fdb_flow(self, port_info, remote_ip, lvm, ofport): + '''Add flow for fdb + + This method assumes to be used by method fdb_add_tun. + We expect to add a flow entry to send a packet to specified port + on bridge. + And you may edit some information for local arp respond. + + :param port_info: list to include mac and ip. + [mac, ip] + :remote_ip: remote ip address. + :param lvm: a local VLAN map of network. + :param ofport: a port to add. + ''' + pass + + @abc.abstractmethod + def del_fdb_flow(self, port_info, remote_ip, lvm, ofport): + '''Delete flow for fdb + + This method assumes to be used by method fdb_remove_tun. + We expect to delete a flow entry to send a packet to specified port + from bridge. + And you may delete some information for local arp respond. + + :param port_info: a list to contain mac and ip. + [mac, ip] + :remote_ip: remote ip address. + :param lvm: local VLAN map of network. + :param ofport: a port to delete. + ''' + pass + + @abc.abstractmethod + def setup_tunnel_port(self, remote_ip, network_type): + '''Setup an added tunnel port. + + This method assumes to be used by method fdb_add_tun. + We expect to prepare to call add_fdb_flow. It will be mainly adding + a port to a bridge. + If you need, you may do some preparation for a bridge. + + :param remote_ip: an ip for port to setup. + :param network_type: a type of network. + :returns: a ofport value. the value 0 means to be unavailable port. + ''' + pass + + @abc.abstractmethod + def cleanup_tunnel_port(self, tun_ofport, tunnel_type): + '''Clean up a deleted tunnel port. + + This method assumes to be used by method fdb_remove_tun. + We expect to clean up after calling del_fdb_flow. It will be mainly + deleting a port from a bridge. + If you need, you may do some cleanup for a bridge. + + :param tun_ofport: a port value to cleanup. + :param tunnel_type: a type of tunnel. + ''' + pass + + def get_agent_ports(self, fdb_entries, local_vlan_map): + for network_id, values in fdb_entries.items(): + lvm = local_vlan_map.get(network_id) + agent_ports = values.get('ports') if lvm else {} + yield (lvm, agent_ports) + + @log.log + def fdb_add_tun(self, context, lvm, agent_ports, ofports): + for remote_ip, ports in agent_ports.items(): + # Ensure we have a tunnel port with this remote agent + ofport = ofports[lvm.network_type].get(remote_ip) + if not ofport: + ofport = self.setup_tunnel_port(remote_ip, lvm.network_type) + if ofport == 0: + continue + for port in ports: + self.add_fdb_flow(port, remote_ip, lvm, ofport) + + @log.log + def fdb_remove_tun(self, context, lvm, agent_ports, ofports): + for remote_ip, ports in agent_ports.items(): + ofport = ofports[lvm.network_type].get(remote_ip) + if not ofport: + continue + for port in ports: + self.del_fdb_flow(port, remote_ip, lvm, ofport) + if port == n_const.FLOODING_ENTRY: + # Check if this tunnel port is still used + self.cleanup_tunnel_port(ofport, lvm.network_type) + + @log.log + def fdb_update(self, context, fdb_entries): + '''Call methods named '_fdb_'. + + This method assumes that methods '_fdb_' are defined in class. + Currently the following actions are available. + chg_ip + ''' + for action, values in fdb_entries.items(): + method = '_fdb_' + action + if not hasattr(self, method): + raise NotImplementedError() + + getattr(self, method)(context, values) diff --git a/neutron/plugins/ml2/drivers/l2pop/constants.py b/neutron/plugins/ml2/drivers/l2pop/constants.py index 2c9b7f96f..9ea7b387a 100644 --- a/neutron/plugins/ml2/drivers/l2pop/constants.py +++ b/neutron/plugins/ml2/drivers/l2pop/constants.py @@ -20,4 +20,5 @@ from neutron.common import constants SUPPORTED_AGENT_TYPES = [constants.AGENT_TYPE_OVS, - constants.AGENT_TYPE_LINUXBRIDGE] + constants.AGENT_TYPE_LINUXBRIDGE, + constants.AGENT_TYPE_OFA] diff --git a/neutron/plugins/ofagent/agent/ofa_neutron_agent.py b/neutron/plugins/ofagent/agent/ofa_neutron_agent.py index f5b497a75..a8bc29d69 100644 --- a/neutron/plugins/ofagent/agent/ofa_neutron_agent.py +++ b/neutron/plugins/ofagent/agent/ofa_neutron_agent.py @@ -26,6 +26,7 @@ from ryu.base import app_manager from ryu.lib import hub from ryu.ofproto import ofproto_v1_3 as ryu_ofp13 +from neutron.agent import l2population_rpc from neutron.agent.linux import ip_lib from neutron.agent.linux import ovs_lib from neutron.agent.linux import polling @@ -161,13 +162,16 @@ class OFANeutronAgentRyuApp(app_manager.RyuApp): class OFANeutronAgent(n_rpc.RpcCallback, - sg_rpc.SecurityGroupAgentRpcCallbackMixin): + sg_rpc.SecurityGroupAgentRpcCallbackMixin, + l2population_rpc.L2populationRpcCallBackTunnelMixin): """A agent for OpenFlow Agent ML2 mechanism driver. OFANeutronAgent is a OpenFlow Agent agent for a ML2 plugin. This is as a ryu application thread. + This has the following features. - An agent acts as an OpenFlow controller on each compute nodes. - OpenFlow 1.3 (vendor agnostic unlike OVS extensions). + - l2-population is mandatory. """ # history @@ -178,8 +182,7 @@ class OFANeutronAgent(n_rpc.RpcCallback, def __init__(self, ryuapp, integ_br, tun_br, local_ip, bridge_mappings, root_helper, polling_interval, tunnel_types=None, - veth_mtu=None, l2_population=False, - minimize_polling=False, + veth_mtu=None, minimize_polling=False, ovsdb_monitor_respawn_interval=( constants.DEFAULT_OVSDBMON_RESPAWN)): """Constructor. @@ -208,7 +211,6 @@ class OFANeutronAgent(n_rpc.RpcCallback, self.available_local_vlans = set(xrange(n_const.MIN_VLAN_TAG, n_const.MAX_VLAN_TAG)) self.tunnel_types = tunnel_types or [] - self.l2_pop = l2_population self.agent_state = { 'binary': 'neutron-ofa-agent', 'host': cfg.CONF.host, @@ -216,7 +218,7 @@ class OFANeutronAgent(n_rpc.RpcCallback, 'configurations': {'bridge_mappings': bridge_mappings, 'tunnel_types': self.tunnel_types, 'tunneling_ip': local_ip, - 'l2_population': self.l2_pop}, + 'l2_population': True}, 'agent_type': n_const.AGENT_TYPE_OFA, 'start_flag': True} @@ -291,8 +293,8 @@ class OFANeutronAgent(n_rpc.RpcCallback, # Define the listening consumers for the agent consumers = [[topics.PORT, topics.UPDATE], [topics.NETWORK, topics.DELETE], - [constants.TUNNEL, topics.UPDATE], - [topics.SECURITY_GROUP, topics.UPDATE]] + [topics.SECURITY_GROUP, topics.UPDATE], + [topics.L2POPULATION, topics.UPDATE, cfg.CONF.host]] self.connection = agent_rpc.create_consumers(self.endpoints, self.topic, consumers) @@ -341,45 +343,100 @@ class OFANeutronAgent(n_rpc.RpcCallback, # they are not used since there is no guarantee the notifications # are processed in the same order as the relevant API requests self.updated_ports.add(ports.get_normalized_port_name(port['id'])) - LOG.debug(_("port_update received port %s"), port['id']) + LOG.debug("port_update received port %s", port['id']) + + def fdb_add(self, context, fdb_entries): + LOG.debug("fdb_add received") + for lvm, agent_ports in self.get_agent_ports(fdb_entries, + self.local_vlan_map): + agent_ports.pop(self.local_ip, None) + if len(agent_ports): + self.fdb_add_tun(context, lvm, agent_ports, + self.tun_br_ofports) + + def fdb_remove(self, context, fdb_entries): + LOG.debug("fdb_remove received") + for lvm, agent_ports in self.get_agent_ports(fdb_entries, + self.local_vlan_map): + agent_ports.pop(self.local_ip, None) + if len(agent_ports): + self.fdb_remove_tun(context, lvm, agent_ports, + self.tun_br_ofports) + + def _add_fdb_flooding_flow(self, lvm): + datapath = self.tun_br.datapath + ofp = datapath.ofproto + ofpp = datapath.ofproto_parser + match = ofpp.OFPMatch( + vlan_vid=int(lvm.vlan) | ofp.OFPVID_PRESENT) + actions = [ofpp.OFPActionPopVlan(), + ofpp.OFPActionSetField( + tunnel_id=int(lvm.segmentation_id))] + for tun_ofport in lvm.tun_ofports: + actions.append(ofpp.OFPActionOutput(int(tun_ofport), 0)) + instructions = [ofpp.OFPInstructionActions( + ofp.OFPIT_APPLY_ACTIONS, actions)] + msg = ofpp.OFPFlowMod(datapath, + table_id=constants.FLOOD_TO_TUN, + command=ofp.OFPFC_ADD, + priority=1, + match=match, instructions=instructions) + self.ryu_send_msg(msg) - def tunnel_update(self, context, **kwargs): - LOG.debug(_("tunnel_update received")) - if not self.enable_tunneling: - return - tunnel_ip = kwargs.get('tunnel_ip') - tunnel_type = kwargs.get('tunnel_type') - if not tunnel_type: - LOG.error(_("No tunnel_type specified, cannot create tunnels")) - return - if tunnel_type not in self.tunnel_types: - LOG.error(_("tunnel_type %s not supported by agent"), tunnel_type) - return - if tunnel_ip == self.local_ip: - return - tun_name = self._create_tunnel_port_name(tunnel_type, tunnel_ip) - if not tun_name: - return - self.setup_tunnel_port(tun_name, tunnel_ip, tunnel_type) + def add_fdb_flow(self, port_info, remote_ip, lvm, ofport): + datapath = self.tun_br.datapath + ofp = datapath.ofproto + ofpp = datapath.ofproto_parser + if port_info == n_const.FLOODING_ENTRY: + lvm.tun_ofports.add(ofport) + self._add_fdb_flooding_flow(lvm) + else: + match = ofpp.OFPMatch( + vlan_vid=int(lvm.vlan) | ofp.OFPVID_PRESENT, + eth_dst=port_info[0]) + actions = [ofpp.OFPActionPopVlan(), + ofpp.OFPActionSetField( + tunnel_id=int(lvm.segmentation_id)), + ofpp.OFPActionOutput(int(ofport), 0)] + instructions = [ofpp.OFPInstructionActions( + ofp.OFPIT_APPLY_ACTIONS, actions)] + msg = ofpp.OFPFlowMod(datapath, + table_id=constants.UCAST_TO_TUN, + command=ofp.OFPFC_ADD, + priority=2, + match=match, instructions=instructions) + self.ryu_send_msg(msg) - def _provision_local_vlan_outbound_for_tunnel(self, lvid, - segmentation_id, ofports): - br = self.tun_br - match = br.ofparser.OFPMatch( - vlan_vid=int(lvid) | ryu_ofp13.OFPVID_PRESENT) - actions = [br.ofparser.OFPActionPopVlan(), - br.ofparser.OFPActionSetField( - tunnel_id=int(segmentation_id))] - for ofport in ofports: - actions.append(br.ofparser.OFPActionOutput(ofport, 0)) - instructions = [br.ofparser.OFPInstructionActions( - ryu_ofp13.OFPIT_APPLY_ACTIONS, actions)] - msg = br.ofparser.OFPFlowMod( - br.datapath, - table_id=constants.FLOOD_TO_TUN, - priority=1, - match=match, instructions=instructions) - self.ryu_send_msg(msg) + def del_fdb_flow(self, port_info, remote_ip, lvm, ofport): + datapath = self.tun_br.datapath + ofp = datapath.ofproto + ofpp = datapath.ofproto_parser + if port_info == n_const.FLOODING_ENTRY: + lvm.tun_ofports.remove(ofport) + if len(lvm.tun_ofports) > 0: + self._add_fdb_flooding_flow(lvm) + else: + # This local vlan doesn't require any more tunelling + match = ofpp.OFPMatch( + vlan_vid=int(lvm.vlan) | ofp.OFPVID_PRESENT) + msg = ofpp.OFPFlowMod(datapath, + table_id=constants.FLOOD_TO_TUN, + command=ofp.OFPFC_DELETE, + out_group=ofp.OFPG_ANY, + out_port=ofp.OFPP_ANY, + match=match) + self.ryu_send_msg(msg) + else: + match = ofpp.OFPMatch( + vlan_vid=int(lvm.vlan) | ofp.OFPVID_PRESENT, + eth_dst=port_info[0]) + msg = ofpp.OFPFlowMod(datapath, + table_id=constants.UCAST_TO_TUN, + command=ofp.OFPFC_DELETE, + out_group=ofp.OFPG_ANY, + out_port=ofp.OFPP_ANY, + match=match) + self.ryu_send_msg(msg) def _provision_local_vlan_inbound_for_tunnel(self, lvid, network_type, segmentation_id): @@ -404,11 +461,6 @@ class OFANeutronAgent(n_rpc.RpcCallback, self.ryu_send_msg(msg) def _local_vlan_for_tunnel(self, lvid, network_type, segmentation_id): - ofports = [int(ofport) for ofport in - self.tun_br_ofports[network_type].values()] - if ofports: - self._provision_local_vlan_outbound_for_tunnel( - lvid, segmentation_id, ofports) self._provision_local_vlan_inbound_for_tunnel(lvid, network_type, segmentation_id) @@ -588,6 +640,9 @@ class OFANeutronAgent(n_rpc.RpcCallback, out_port=ryu_ofp13.OFPP_ANY, match=match) self.ryu_send_msg(msg) + # 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 in (p_const.TYPE_FLAT, p_const.TYPE_VLAN): if lvm.physical_network in self.phys_brs: self._reclaim_local_vlan_outbound(lvm) @@ -1015,7 +1070,7 @@ class OFANeutronAgent(n_rpc.RpcCallback, else: LOG.debug(_("No VIF port for port %s defined on agent."), port_id) - def setup_tunnel_port(self, port_name, remote_ip, tunnel_type): + def _setup_tunnel_port(self, port_name, remote_ip, tunnel_type): ofport = self.tun_br.add_tunnel_port(port_name, remote_ip, self.local_ip, @@ -1044,36 +1099,45 @@ class OFANeutronAgent(n_rpc.RpcCallback, match=match, instructions=instructions) self.ryu_send_msg(msg) + return ofport - ofports = [int(p) for p in self.tun_br_ofports[tunnel_type].values()] - if ofports: - # Update flooding flows to include the new tunnel - for network_id, vlan_mapping in self.local_vlan_map.iteritems(): - if vlan_mapping.network_type == tunnel_type: - match = self.tun_br.ofparser.OFPMatch( - vlan_vid=int(vlan_mapping.vlan) | - ryu_ofp13.OFPVID_PRESENT) - actions = [ - self.tun_br.ofparser.OFPActionPopVlan(), - self.tun_br.ofparser.OFPActionSetField( - tunnel_id=int(vlan_mapping.segmentation_id))] - actions.extend( - self.tun_br.ofparser.OFPActionOutput(p, 0) - for p in ofports - ) - instructions = [ - self.tun_br.ofparser.OFPInstructionActions( - ryu_ofp13.OFPIT_APPLY_ACTIONS, - actions)] - msg = self.tun_br.ofparser.OFPFlowMod( - self.tun_br.datapath, - table_id=constants.FLOOD_TO_TUN, - priority=1, - match=match, - instructions=instructions) - self.ryu_send_msg(msg) + def setup_tunnel_port(self, remote_ip, network_type): + port_name = self._create_tunnel_port_name(network_type, remote_ip) + if not port_name: + return 0 + ofport = self._setup_tunnel_port(port_name, + remote_ip, + network_type) return ofport + def _remove_tunnel_port(self, tun_ofport, tunnel_type): + datapath = self.tun_br.datapath + ofp = datapath.ofproto + ofpp = datapath.ofproto_parser + for remote_ip, ofport in self.tun_br_ofports[tunnel_type].items(): + if ofport == tun_ofport: + port_name = self._create_tunnel_port_name(tunnel_type, + remote_ip) + if port_name: + self.tun_br.delete_port(port_name) + match = ofpp.OFPMatch(in_port=int(ofport)) + msg = ofpp.OFPFlowMod(datapath, + command=ofp.OFPFC_DELETE, + out_group=ofp.OFPG_ANY, + out_port=ofp.OFPP_ANY, + match=match) + self.ryu_send_msg(msg) + self.tun_br_ofports[tunnel_type].pop(remote_ip, None) + + 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: + self._remove_tunnel_port(tun_ofport, tunnel_type) + def treat_devices_added_or_updated(self, devices): resync = False all_ports = dict((p.normalized_port_name(), p) for p in @@ -1245,19 +1309,9 @@ class OFANeutronAgent(n_rpc.RpcCallback, resync = False try: for tunnel_type in self.tunnel_types: - 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']: - tun_name = self._create_tunnel_port_name( - tunnel_type, tunnel['ip_address']) - if not tun_name: - continue - self.setup_tunnel_port(tun_name, - tunnel['ip_address'], - tunnel_type) + self.plugin_rpc.tunnel_sync(self.context, + self.local_ip, + 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}) @@ -1428,7 +1482,6 @@ def create_agent_config_map(config): minimize_polling=config.AGENT.minimize_polling, tunnel_types=config.AGENT.tunnel_types, veth_mtu=config.AGENT.veth_mtu, - l2_population=False, ovsdb_monitor_respawn_interval=constants.DEFAULT_OVSDBMON_RESPAWN, ) diff --git a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py index 4f8c2b60b..956ef7082 100644 --- a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py @@ -97,7 +97,7 @@ class OVSSecurityGroupAgent(sg_rpc.SecurityGroupAgentRpcMixin): class OVSNeutronAgent(n_rpc.RpcCallback, sg_rpc.SecurityGroupAgentRpcCallbackMixin, - l2population_rpc.L2populationRpcCallBackMixin, + l2population_rpc.L2populationRpcCallBackTunnelMixin, dvr_rpc.DVRAgentRpcCallbackMixin): '''Implements OVS-based tunneling, VLANs and flat networks. @@ -348,61 +348,35 @@ class OVSNeutronAgent(n_rpc.RpcCallback, return tun_name = '%s-%s' % (tunnel_type, tunnel_id) if not self.l2_pop: - self.setup_tunnel_port(tun_name, tunnel_ip, tunnel_type) + 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') + LOG.debug("fdb_add received") + for lvm, agent_ports in self.get_agent_ports(fdb_entries, + self.local_vlan_map): agent_ports.pop(self.local_ip, None) if len(agent_ports): if not self.enable_distributed_routing: 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: - remote_ip_hex = self.get_ip_in_hex(agent_ip) - if not remote_ip_hex: - continue - port_name = '%s-%s' % (lvm.network_type, remote_ip_hex) - 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.fdb_add_tun(context, lvm, agent_ports, + self.tun_br_ofports) if not self.enable_distributed_routing: 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') + LOG.debug("fdb_remove received") + for lvm, agent_ports in self.get_agent_ports(fdb_entries, + self.local_vlan_map): agent_ports.pop(self.local_ip, None) if len(agent_ports): if not self.enable_distributed_routing: 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.fdb_remove_tun(context, lvm, agent_ports, + self.tun_br_ofports) if not self.enable_distributed_routing: self.tun_br.defer_apply_off() - def _add_fdb_flow(self, port_info, agent_ip, lvm, ofport): + def add_fdb_flow(self, port_info, remote_ip, lvm, ofport): if port_info == q_const.FLOODING_ENTRY: lvm.tun_ofports.add(ofport) ofports = ','.join(lvm.tun_ofports) @@ -422,7 +396,7 @@ class OVSNeutronAgent(n_rpc.RpcCallback, "output:%s" % (lvm.segmentation_id, ofport)) - def _del_fdb_flow(self, port_info, agent_ip, lvm, ofport): + def del_fdb_flow(self, port_info, remote_ip, lvm, ofport): if port_info == q_const.FLOODING_ENTRY: lvm.tun_ofports.remove(ofport) if len(lvm.tun_ofports) > 0: @@ -436,8 +410,6 @@ class OVSNeutronAgent(n_rpc.RpcCallback, # This local vlan doesn't require any more tunnelling 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: self._set_arp_responder('remove', lvm.vlan, port_info[0], port_info[1]) @@ -486,15 +458,6 @@ class OVSNeutronAgent(n_rpc.RpcCallback, 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(): - method = '_fdb_' + action - if not hasattr(self, method): - raise NotImplementedError() - - getattr(self, method)(context, values) - def _set_arp_responder(self, action, lvid, mac_str, ip_str): '''Set the ARP respond entry. @@ -1111,7 +1074,7 @@ class OVSNeutronAgent(n_rpc.RpcCallback, else: LOG.debug(_("No VIF port for port %s defined on agent."), port_id) - def setup_tunnel_port(self, port_name, remote_ip, tunnel_type): + def _setup_tunnel_port(self, port_name, remote_ip, tunnel_type): ofport = self.tun_br.add_tunnel_port(port_name, remote_ip, self.local_ip, @@ -1150,6 +1113,16 @@ class OVSNeutronAgent(n_rpc.RpcCallback, ofports)) return ofport + def setup_tunnel_port(self, remote_ip, network_type): + remote_ip_hex = self.get_ip_in_hex(remote_ip) + if not remote_ip_hex: + return 0 + port_name = '%s-%s' % (network_type, remote_ip_hex) + ofport = self._setup_tunnel_port(port_name, + remote_ip, + network_type) + 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(): @@ -1393,9 +1366,8 @@ class OVSNeutronAgent(n_rpc.RpcCallback, continue tun_name = '%s-%s' % (tunnel_type, tunnel_id or remote_ip_hex) - self.setup_tunnel_port(tun_name, - tunnel['ip_address'], - tunnel_type) + 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}) diff --git a/neutron/tests/unit/agent/l2population_rpc_base.py b/neutron/tests/unit/agent/l2population_rpc_base.py new file mode 100644 index 000000000..563e057ae --- /dev/null +++ b/neutron/tests/unit/agent/l2population_rpc_base.py @@ -0,0 +1,140 @@ +# Copyright (C) 2014 VA Linux Systems Japan K.K. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# @author: Fumihiko Kakuma, VA Linux Systems Japan K.K. + +import collections + +from neutron.agent import l2population_rpc +from neutron.plugins.openvswitch.agent import ovs_neutron_agent +from neutron.tests import base + + +class FakeNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin): + + def fdb_add(self, context, fdb_entries): + pass + + def fdb_remove(self, context, fdb_entries): + pass + + def add_fdb_flow(self, port_info, remote_ip, lvm, ofport): + pass + + def del_fdb_flow(self, port_info, remote_ip, lvm, ofport): + pass + + def setup_tunnel_port(self, remote_ip, network_type): + pass + + def cleanup_tunnel_port(self, tun_ofport, tunnel_type): + pass + + +class TestL2populationRpcCallBackTunnelMixinBase(base.BaseTestCase): + + def setUp(self): + super(TestL2populationRpcCallBackTunnelMixinBase, self).setUp() + self.fakeagent = FakeNeutronAgent() + Port = collections.namedtuple('Port', 'ip, ofport') + LVM = collections.namedtuple( + 'LVM', 'net, vlan, phys, segid, mac, ip, vif, port') + + self.local_ip = '127.0.0.1' + self.type_gre = 'gre' + self.ports = [Port(ip='10.1.0.1', ofport='ofport1'), + Port(ip='10.1.0.2', ofport='ofport2'), + Port(ip='10.1.0.3', ofport='ofport3')] + self.ofports = { + self.type_gre: { + self.ports[0].ip: self.ports[0].ofport, + self.ports[1].ip: self.ports[1].ofport, + self.ports[2].ip: self.ports[2].ofport, + } + } + + self.lvms = [LVM(net='net1', vlan=1, phys='phys1', segid='tun1', + mac='mac1', ip='1.1.1.1', vif='vifid1', + port='port1'), + LVM(net='net2', vlan=2, phys='phys2', segid='tun2', + mac='mac2', ip='2.2.2.2', vif='vifid2', + port='port2'), + LVM(net='net3', vlan=3, phys='phys3', segid='tun3', + mac='mac3', ip='3.3.3.3', vif='vifid3', + port='port3')] + + self.agent_ports = { + self.ports[0].ip: [[self.lvms[0].mac, self.lvms[0].ip]], + self.ports[1].ip: [[self.lvms[1].mac, self.lvms[1].ip]], + self.ports[2].ip: [[self.lvms[2].mac, self.lvms[2].ip]], + } + + self.fdb_entries1 = { + self.lvms[0].net: { + 'network_type': self.type_gre, + 'segment_id': self.lvms[0].segid, + 'ports': { + self.local_ip: [], + self.ports[0].ip: [[self.lvms[0].mac, self.lvms[0].ip]]}, + }, + self.lvms[1].net: { + 'network_type': self.type_gre, + 'segment_id': self.lvms[1].segid, + 'ports': { + self.local_ip: [], + self.ports[1].ip: [[self.lvms[1].mac, self.lvms[1].ip]]}, + }, + self.lvms[2].net: { + 'network_type': self.type_gre, + 'segment_id': self.lvms[2].segid, + 'ports': { + self.local_ip: [], + self.ports[2].ip: [[self.lvms[2].mac, self.lvms[2].ip]]}, + }, + } + + self.lvm1 = ovs_neutron_agent.LocalVLANMapping( + self.lvms[0].vlan, self.type_gre, self.lvms[0].phys, + self.lvms[0].segid, {self.lvms[0].vif: self.lvms[0].port}) + self.lvm2 = ovs_neutron_agent.LocalVLANMapping( + self.lvms[1].vlan, self.type_gre, self.lvms[1].phys, + self.lvms[1].segid, {self.lvms[1].vif: self.lvms[1].port}) + self.lvm3 = ovs_neutron_agent.LocalVLANMapping( + self.lvms[2].vlan, self.type_gre, self.lvms[2].phys, + self.lvms[2].segid, {self.lvms[2].vif: self.lvms[2].port}) + + self.local_vlan_map1 = { + self.lvms[0].net: self.lvm1, + self.lvms[1].net: self.lvm2, + self.lvms[2].net: self.lvm3, + } + + self.upd_fdb_entry1_val = { + self.lvms[0].net: { + self.ports[0].ip: { + 'before': [[self.lvms[0].mac, self.lvms[0].ip]], + 'after': [[self.lvms[1].mac, self.lvms[1].ip]], + }, + self.ports[1].ip: { + 'before': [[self.lvms[0].mac, self.lvms[0].ip]], + 'after': [[self.lvms[1].mac, self.lvms[1].ip]], + }, + }, + self.lvms[1].net: { + self.ports[2].ip: { + 'before': [[self.lvms[0].mac, self.lvms[0].ip]], + 'after': [[self.lvms[2].mac, self.lvms[2].ip]], + }, + }, + } + self.upd_fdb_entry1 = {'chg_ip': self.upd_fdb_entry1_val} diff --git a/neutron/tests/unit/agent/test_l2population_rpc.py b/neutron/tests/unit/agent/test_l2population_rpc.py new file mode 100644 index 000000000..3f542198d --- /dev/null +++ b/neutron/tests/unit/agent/test_l2population_rpc.py @@ -0,0 +1,190 @@ +# Copyright (C) 2014 VA Linux Systems Japan K.K. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# @author: Fumihiko Kakuma, VA Linux Systems Japan K.K. + +import contextlib + +import mock + +from neutron.common import constants as n_const +from neutron.tests.unit.agent import l2population_rpc_base + + +class TestL2populationRpcCallBackTunnelMixin( + l2population_rpc_base.TestL2populationRpcCallBackTunnelMixinBase): + + def test_get_agent_ports_no_data(self): + for lvm, agent_ports in self.fakeagent.get_agent_ports( + self.fdb_entries1, {}): + self.assertIsNone(lvm) + self.assertEqual({}, agent_ports) + + def test_get_agent_ports_non_existence_key_in_lvm(self): + results = {} + del self.local_vlan_map1[self.lvms[1].net] + for lvm, agent_ports in self.fakeagent.get_agent_ports( + self.fdb_entries1, self.local_vlan_map1): + results[lvm] = agent_ports + expected = { + self.lvm1: { + self.ports[0].ip: [[self.lvms[0].mac, self.lvms[0].ip]], + self.local_ip: []}, + None: {}, + self.lvm3: { + self.ports[2].ip: [[self.lvms[2].mac, self.lvms[2].ip]], + self.local_ip: []}, + } + self.assertEqual(expected, results) + + def test_get_agent_ports_no_agent_ports(self): + results = {} + self.fdb_entries1[self.lvms[1].net]['ports'] = {} + for lvm, agent_ports in self.fakeagent.get_agent_ports( + self.fdb_entries1, self.local_vlan_map1): + results[lvm] = agent_ports + expected = { + self.lvm1: { + self.ports[0].ip: [[self.lvms[0].mac, self.lvms[0].ip]], + self.local_ip: []}, + self.lvm2: {}, + self.lvm3: { + self.ports[2].ip: [[self.lvms[2].mac, self.lvms[2].ip]], + self.local_ip: []}, + } + self.assertEqual(expected, results) + + def test_fdb_add_tun(self): + with contextlib.nested( + mock.patch.object(self.fakeagent, 'setup_tunnel_port'), + mock.patch.object(self.fakeagent, 'add_fdb_flow'), + ) as (mock_setup_tunnel_port, mock_add_fdb_flow): + self.fakeagent.fdb_add_tun('context', self.lvm1, + self.agent_ports, self.ofports) + expected = [ + mock.call([self.lvms[0].mac, self.lvms[0].ip], self.ports[0].ip, + self.lvm1, self.ports[0].ofport), + mock.call([self.lvms[1].mac, self.lvms[1].ip], self.ports[1].ip, + self.lvm1, self.ports[1].ofport), + mock.call([self.lvms[2].mac, self.lvms[2].ip], self.ports[2].ip, + self.lvm1, self.ports[2].ofport), + ] + self.assertEqual(sorted(expected), + sorted(mock_add_fdb_flow.call_args_list)) + + def test_fdb_add_tun_non_existence_key_in_ofports(self): + ofport = self.lvm1.network_type + '0a0a0a0a' + del self.ofports[self.type_gre][self.ports[1].ip] + with contextlib.nested( + mock.patch.object(self.fakeagent, 'setup_tunnel_port', + return_value=ofport), + mock.patch.object(self.fakeagent, 'add_fdb_flow'), + ) as (mock_setup_tunnel_port, mock_add_fdb_flow): + self.fakeagent.fdb_add_tun('context', self.lvm1, + self.agent_ports, self.ofports) + mock_setup_tunnel_port.assert_called_once_with( + self.ports[1].ip, self.lvm1.network_type) + expected = [ + mock.call([self.lvms[0].mac, self.lvms[0].ip], self.ports[0].ip, + self.lvm1, self.ports[0].ofport), + mock.call([self.lvms[1].mac, self.lvms[1].ip], self.ports[1].ip, + self.lvm1, ofport), + mock.call([self.lvms[2].mac, self.lvms[2].ip], self.ports[2].ip, + self.lvm1, self.ports[2].ofport), + ] + self.assertEqual(sorted(expected), + sorted(mock_add_fdb_flow.call_args_list)) + + def test_fdb_add_tun_unavailable_ofport(self): + del self.ofports[self.type_gre][self.ports[1].ip] + with contextlib.nested( + mock.patch.object(self.fakeagent, 'setup_tunnel_port', + return_value=0), + mock.patch.object(self.fakeagent, 'add_fdb_flow'), + ) as (mock_setup_tunnel_port, mock_add_fdb_flow): + self.fakeagent.fdb_add_tun('context', self.lvm1, + self.agent_ports, self.ofports) + mock_setup_tunnel_port.assert_called_once_with( + self.ports[1].ip, self.lvm1.network_type) + expected = [ + mock.call([self.lvms[0].mac, self.lvms[0].ip], self.ports[0].ip, + self.lvm1, self.ports[0].ofport), + mock.call([self.lvms[2].mac, self.lvms[2].ip], self.ports[2].ip, + self.lvm1, self.ports[2].ofport), + ] + self.assertEqual(sorted(expected), + sorted(mock_add_fdb_flow.call_args_list)) + + def test_fdb_remove_tun(self): + with mock.patch.object( + self.fakeagent, 'del_fdb_flow') as mock_del_fdb_flow: + self.fakeagent.fdb_remove_tun('context', self.lvm1, + self.agent_ports, self.ofports) + expected = [ + mock.call([self.lvms[0].mac, self.lvms[0].ip], self.ports[0].ip, + self.lvm1, self.ports[0].ofport), + mock.call([self.lvms[1].mac, self.lvms[1].ip], self.ports[1].ip, + self.lvm1, self.ports[1].ofport), + mock.call([self.lvms[2].mac, self.lvms[2].ip], self.ports[2].ip, + self.lvm1, self.ports[2].ofport), + ] + self.assertEqual(sorted(expected), + sorted(mock_del_fdb_flow.call_args_list)) + + def test_fdb_remove_tun_flooding_entry(self): + self.agent_ports[self.ports[1].ip] = [n_const.FLOODING_ENTRY] + with contextlib.nested( + mock.patch.object(self.fakeagent, 'del_fdb_flow'), + mock.patch.object(self.fakeagent, 'cleanup_tunnel_port'), + ) as (mock_del_fdb_flow, mock_cleanup_tunnel_port): + self.fakeagent.fdb_remove_tun('context', self.lvm1, + self.agent_ports, self.ofports) + expected = [ + mock.call([self.lvms[0].mac, self.lvms[0].ip], self.ports[0].ip, + self.lvm1, self.ports[0].ofport), + mock.call([n_const.FLOODING_ENTRY[0], n_const.FLOODING_ENTRY[1]], + self.ports[1].ip, self.lvm1, self.ports[1].ofport), + mock.call([self.lvms[2].mac, self.lvms[2].ip], self.ports[2].ip, + self.lvm1, self.ports[2].ofport), + ] + self.assertEqual(sorted(expected), + sorted(mock_del_fdb_flow.call_args_list)) + mock_cleanup_tunnel_port.assert_called_once_with( + self.ports[1].ofport, self.lvm1.network_type) + + def test_fdb_remove_tun_non_existence_key_in_ofports(self): + del self.ofports[self.type_gre][self.ports[1].ip] + with mock.patch.object( + self.fakeagent, 'del_fdb_flow') as mock_del_fdb_flow: + self.fakeagent.fdb_remove_tun('context', self.lvm1, + self.agent_ports, self.ofports) + expected = [ + mock.call([self.lvms[0].mac, self.lvms[0].ip], self.ports[0].ip, + self.lvm1, self.ports[0].ofport), + mock.call([self.lvms[2].mac, self.lvms[2].ip], self.ports[2].ip, + self.lvm1, self.ports[2].ofport), + ] + self.assertEqual(sorted(expected), + sorted(mock_del_fdb_flow.call_args_list)) + + def test_fdb_update(self): + fake__fdb_chg_ip = mock.Mock() + self.fakeagent._fdb_chg_ip = fake__fdb_chg_ip + self.fakeagent.fdb_update('context', self.upd_fdb_entry1) + fake__fdb_chg_ip.assert_called_once_with( + 'context', self.upd_fdb_entry1_val) + + def test_fdb_update_non_existence_method(self): + self.assertRaises(NotImplementedError, + self.fakeagent.fdb_update, + 'context', self.upd_fdb_entry1) diff --git a/neutron/tests/unit/ofagent/test_ofa_neutron_agent.py b/neutron/tests/unit/ofagent/test_ofa_neutron_agent.py index 406480d80..aaec36c9d 100644 --- a/neutron/tests/unit/ofagent/test_ofa_neutron_agent.py +++ b/neutron/tests/unit/ofagent/test_ofa_neutron_agent.py @@ -18,6 +18,7 @@ # @author: Fumihiko Kakuma, VA Linux Systems Japan K.K. # @author: YAMAMOTO Takashi, VA Linux Systems Japan K.K. +import collections import contextlib import mock @@ -27,6 +28,7 @@ import testtools from neutron.agent.linux import ip_lib from neutron.agent.linux import utils +from neutron.common import constants as n_const from neutron.openstack.common import importutils from neutron.plugins.common import constants as p_const from neutron.plugins.openvswitch.common import constants @@ -258,27 +260,36 @@ class TestOFANeutronAgent(OFAAgentTestCase): 'FixedIntervalLoopingCall', new=MockFixedIntervalLoopingCall)): self.agent = self.mod_agent.OFANeutronAgent(self.ryuapp, **kwargs) - self.agent.tun_br = _mk_test_br('tun_br') - self.datapath = mock.Mock() - self.ofparser = mock.Mock() - self.agent.phys_brs['phys-net1'] = _mk_test_br('phys_br1') - self.agent.phys_ofports['phys-net1'] = 777 - self.agent.int_ofports['phys-net1'] = 666 - self.datapath.ofparser = self.ofparser - self.ofparser.OFPMatch = mock.Mock() - self.ofparser.OFPMatch.return_value = mock.Mock() - self.ofparser.OFPFlowMod = mock.Mock() - self.ofparser.OFPFlowMod.return_value = mock.Mock() - self.agent.int_br.ofparser = self.ofparser - self.agent.int_br.datapath = _mk_test_dp('int_br') self.agent.sg_agent = mock.Mock() + self.int_dp = _mk_test_dp('int_br') + self.agent.int_br.ofparser = self.int_dp.ofproto_parser + self.agent.int_br.datapath = self.int_dp + self.agent.tun_br = _mk_test_br('tun_br') + self.agent.phys_brs['phys-net1'] = _mk_test_br('phys_br1') + self.agent.phys_ofports['phys-net1'] = 777 + self.agent.int_ofports['phys-net1'] = 666 + self.datapath = _mk_test_dp('phys_br') + + def _create_tunnel_port_name(self, tunnel_ip, tunnel_type): + tunnel_ip_hex = '%08x' % netaddr.IPAddress(tunnel_ip, version=4) + return '%s-%s' % (tunnel_type, tunnel_ip_hex) def _mock_port_bound(self, ofport=None, new_local_vlan=None, old_local_vlan=None): port = mock.Mock() port.ofport = ofport net_uuid = 'my-net-uuid' + ofp = self.agent.int_br.datapath.ofproto + ofpp = self.agent.int_br.datapath.ofproto_parser + expected_msg = ofpp.OFPFlowMod( + self.agent.int_br.datapath, + match=ofpp.OFPMatch(in_port=port.ofport), + table_id=ofp.OFPTT_ALL, + command=ofp.OFPFC_DELETE, + out_group=ofp.OFPG_ANY, + out_port=ofp.OFPP_ANY + ) if old_local_vlan is not None: self.agent.local_vlan_map[net_uuid] = ( self.mod_agent.LocalVLANMapping( @@ -296,8 +307,7 @@ class TestOFANeutronAgent(OFAAgentTestCase): set_ovs_db_func.assert_called_once_with( "Port", mock.ANY, "tag", str(new_local_vlan)) if ofport != -1: - ryu_send_msg_func.assert_called_once_with( - self.ofparser.OFPFlowMod.return_value) + ryu_send_msg_func.assert_called_once_with(expected_msg) else: self.assertFalse(ryu_send_msg_func.called) else: @@ -316,6 +326,12 @@ class TestOFANeutronAgent(OFAAgentTestCase): def _test_port_dead(self, cur_tag=None): port = mock.Mock() port.ofport = 1 + ofpp = self.agent.int_br.datapath.ofproto_parser + expected_msg = ofpp.OFPFlowMod( + self.agent.int_br.datapath, + priority=2, + match=ofpp.OFPMatch(in_port=port.ofport) + ) with contextlib.nested( mock.patch.object(self.mod_agent.OVSBridge, 'set_db_attribute', return_value=True), @@ -331,8 +347,7 @@ class TestOFANeutronAgent(OFAAgentTestCase): else: set_ovs_db_func.assert_called_once_with( "Port", mock.ANY, "tag", str(self.mod_agent.DEAD_VLAN_TAG)) - ryu_send_msg_func.assert_called_once_with( - self.ofparser.OFPFlowMod.return_value) + ryu_send_msg_func.assert_called_once_with(expected_msg) def test_port_dead(self): self._test_port_dead() @@ -570,14 +585,17 @@ class TestOFANeutronAgent(OFAAgentTestCase): ) 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") + self.assertFalse(clean_tun_fn.called) recl_fn.assert_called_with("123") def test_port_update(self): @@ -655,6 +673,146 @@ class TestOFANeutronAgent(OFAAgentTestCase): self.agent.port_unbound("vif3", "netuid12345") self.assertEqual(reclvl_fn.call_count, 2) + def _prepare_l2_pop_ofports(self): + LVM = collections.namedtuple('LVM', 'net, vlan, segid, ip') + self.lvms = [LVM(net='net1', vlan=11, segid='21', ip='1.1.1.1'), + LVM(net='net2', vlan=12, segid='22', ip='2.2.2.2')] + self.tunnel_type = 'gre' + self.tun_name1 = self._create_tunnel_port_name(self.lvms[0].ip, + self.tunnel_type) + self.tun_name2 = self._create_tunnel_port_name(self.lvms[1].ip, + self.tunnel_type) + lvm1 = mock.Mock() + lvm1.network_type = self.tunnel_type + lvm1.vlan = self.lvms[0].vlan + lvm1.segmentation_id = self.lvms[0].segid + lvm1.tun_ofports = set(['1']) + lvm2 = mock.Mock() + lvm2.network_type = self.tunnel_type + lvm2.vlan = self.lvms[1].vlan + lvm2.segmentation_id = self.lvms[1].segid + lvm2.tun_ofports = set(['1', '2']) + self.agent.local_vlan_map = {self.lvms[0].net: lvm1, + self.lvms[1].net: lvm2} + self.agent.tun_br_ofports = {self.tunnel_type: + {self.lvms[0].ip: '1', + self.lvms[1].ip: '2'}} + + def test_fdb_ignore_network(self): + self._prepare_l2_pop_ofports() + fdb_entry = {'net3': {}} + with contextlib.nested( + mock.patch.object(self.agent, 'ryu_send_msg'), + mock.patch.object(self.agent, '_setup_tunnel_port'), + mock.patch.object(self.agent, 'cleanup_tunnel_port') + ) as (ryu_send_msg_fn, add_tun_fn, clean_tun_fn): + self.agent.fdb_add(None, fdb_entry) + self.assertFalse(add_tun_fn.called) + self.agent.fdb_remove(None, fdb_entry) + self.assertFalse(clean_tun_fn.called) + self.assertFalse(ryu_send_msg_fn.called) + + def test_fdb_ignore_self(self): + self._prepare_l2_pop_ofports() + self.agent.local_ip = 'agent_ip' + fdb_entry = {self.lvms[1].net: + {'network_type': self.tunnel_type, + '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 = {self.lvms[0].net: + {'network_type': self.tunnel_type, + 'segment_id': 'tun1', + 'ports': + {self.lvms[1].ip: + [['mac', 'ip'], + n_const.FLOODING_ENTRY]}}} + with contextlib.nested( + mock.patch.object(self.agent, 'ryu_send_msg'), + mock.patch.object(self.agent.tun_br, '_setup_tunnel_port'), + ) as (ryu_send_msg_fn, add_tun_fn): + add_tun_fn.return_value = '2' + self.agent.fdb_add(None, fdb_entry) + self.assertEqual(ryu_send_msg_fn.call_count, 2) + + def test_fdb_del_flows(self): + self._prepare_l2_pop_ofports() + fdb_entry = {self.lvms[1].net: + {'network_type': self.tunnel_type, + 'segment_id': 'tun2', + 'ports': + {self.lvms[1].ip: + [['mac', 'ip'], + n_const.FLOODING_ENTRY]}}} + with mock.patch.object(self.agent, + 'ryu_send_msg') as ryu_send_msg_fn: + self.agent.fdb_remove(None, fdb_entry) + self.assertEqual(ryu_send_msg_fn.call_count, 3) + + def test_fdb_add_port(self): + self._prepare_l2_pop_ofports() + tunnel_ip = '10.10.10.10' + tun_name = self._create_tunnel_port_name(tunnel_ip, + self.tunnel_type) + fdb_entry = {self.lvms[0].net: + {'network_type': self.tunnel_type, + 'segment_id': 'tun1', + 'ports': {self.lvms[0].ip: [['mac', 'ip']]}}} + with contextlib.nested( + mock.patch.object(self.agent, 'ryu_send_msg'), + mock.patch.object(self.agent, '_setup_tunnel_port') + ) as (ryu_send_msg_fn, add_tun_fn): + self.agent.fdb_add(None, fdb_entry) + self.assertFalse(add_tun_fn.called) + fdb_entry[self.lvms[0].net]['ports'][tunnel_ip] = [['mac', 'ip']] + self.agent.fdb_add(None, fdb_entry) + add_tun_fn.assert_called_with( + tun_name, tunnel_ip, self.tunnel_type) + + def test_fdb_del_port(self): + self._prepare_l2_pop_ofports() + fdb_entry = {self.lvms[1].net: + {'network_type': self.tunnel_type, + 'segment_id': 'tun2', + 'ports': {self.lvms[1].ip: [n_const.FLOODING_ENTRY]}}} + with contextlib.nested( + mock.patch.object(self.agent, 'ryu_send_msg'), + mock.patch.object(self.agent.tun_br, 'delete_port') + ) as (ryu_send_msg_fn, del_port_fn): + self.agent.fdb_remove(None, fdb_entry) + del_port_fn.assert_called_once_with(self.tun_name2) + + def test_recl_lv_port_to_preserve(self): + self._prepare_l2_pop_ofports() + 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(self.lvms[0].net) + self.assertFalse(clean_tun_fn.called) + + def test_recl_lv_port_to_remove(self): + self._prepare_l2_pop_ofports() + self.agent.enable_tunneling = True + with contextlib.nested( + mock.patch.object(self.agent.tun_br, 'delete_port'), + mock.patch.object(self.agent, 'ryu_send_msg') + ) as (del_port_fn, ryu_send_msg_fn): + self.agent.reclaim_local_vlan(self.lvms[1].net) + del_port_fn.assert_called_once_with(self.tun_name2) + def test_daemon_loop_uses_polling_manager(self): with mock.patch( 'neutron.agent.linux.polling.get_polling_manager' @@ -671,13 +829,13 @@ class TestOFANeutronAgent(OFAAgentTestCase): constants.DEFAULT_OVSDBMON_RESPAWN) mock_loop.assert_called_once_with(polling_manager=fake_pm.__enter__()) - def test_setup_tunnel_port_error_negative(self): + def test__setup_tunnel_port_error_negative(self): with contextlib.nested( mock.patch.object(self.agent.tun_br, 'add_tunnel_port', return_value='-1'), mock.patch.object(self.mod_agent.LOG, 'error') ) as (add_tunnel_port_fn, log_error_fn): - ofport = self.agent.setup_tunnel_port( + ofport = self.agent._setup_tunnel_port( 'gre-1', 'remote_ip', p_const.TYPE_GRE) add_tunnel_port_fn.assert_called_once_with( 'gre-1', 'remote_ip', self.agent.local_ip, p_const.TYPE_GRE, @@ -687,14 +845,14 @@ class TestOFANeutronAgent(OFAAgentTestCase): {'type': p_const.TYPE_GRE, 'ip': 'remote_ip'}) self.assertEqual(ofport, 0) - def test_setup_tunnel_port_error_not_int(self): + def test__setup_tunnel_port_error_not_int(self): with contextlib.nested( mock.patch.object(self.agent.tun_br, 'add_tunnel_port', return_value=None), mock.patch.object(self.mod_agent.LOG, 'exception'), mock.patch.object(self.mod_agent.LOG, 'error') ) as (add_tunnel_port_fn, log_exc_fn, log_error_fn): - ofport = self.agent.setup_tunnel_port( + ofport = self.agent._setup_tunnel_port( 'gre-1', 'remote_ip', p_const.TYPE_GRE) add_tunnel_port_fn.assert_called_once_with( 'gre-1', 'remote_ip', self.agent.local_ip, p_const.TYPE_GRE, @@ -707,72 +865,18 @@ class TestOFANeutronAgent(OFAAgentTestCase): {'type': p_const.TYPE_GRE, 'ip': 'remote_ip'}) self.assertEqual(ofport, 0) - def _create_tunnel_port_name(self, tunnel_ip, tunnel_type): - tunnel_ip_hex = '%08x' % netaddr.IPAddress(tunnel_ip, version=4) - return '%s-%s' % (tunnel_type, tunnel_ip_hex) - - def test_tunnel_sync_with_valid_ip_address_and_gre_type(self): - tunnel_ip = '100.101.102.103' - self.agent.tunnel_types = ['gre'] - tun_name = self._create_tunnel_port_name(tunnel_ip, - self.agent.tunnel_types[0]) - fake_tunnel_details = {'tunnels': [{'ip_address': tunnel_ip}]} - with contextlib.nested( - mock.patch.object(self.agent.plugin_rpc, 'tunnel_sync', - return_value=fake_tunnel_details), - mock.patch.object(self.agent, 'setup_tunnel_port') - ) as (tunnel_sync_rpc_fn, setup_tunnel_port_fn): - self.agent.tunnel_sync() - expected_calls = [mock.call(tun_name, tunnel_ip, - self.agent.tunnel_types[0])] - setup_tunnel_port_fn.assert_has_calls(expected_calls) - - def test_tunnel_sync_with_valid_ip_address_and_vxlan_type(self): - tunnel_ip = '100.101.31.15' + def test_tunnel_sync(self): + self.agent.local_ip = 'agent_ip' + self.agent.context = 'fake_context' self.agent.tunnel_types = ['vxlan'] - tun_name = self._create_tunnel_port_name(tunnel_ip, - self.agent.tunnel_types[0]) - fake_tunnel_details = {'tunnels': [{'ip_address': tunnel_ip}]} - with contextlib.nested( - mock.patch.object(self.agent.plugin_rpc, 'tunnel_sync', - return_value=fake_tunnel_details), - mock.patch.object(self.agent, 'setup_tunnel_port') - ) as (tunnel_sync_rpc_fn, setup_tunnel_port_fn): + with mock.patch.object( + self.agent.plugin_rpc, 'tunnel_sync' + ) as tunnel_sync_rpc_fn: self.agent.tunnel_sync() - expected_calls = [mock.call(tun_name, tunnel_ip, - self.agent.tunnel_types[0])] - setup_tunnel_port_fn.assert_has_calls(expected_calls) - - def test_tunnel_sync_invalid_ip_address(self): - tunnel_ip = '100.100.100.100' - self.agent.tunnel_types = ['vxlan'] - tun_name = self._create_tunnel_port_name(tunnel_ip, - self.agent.tunnel_types[0]) - fake_tunnel_details = {'tunnels': [{'ip_address': '300.300.300.300'}, - {'ip_address': tunnel_ip}]} - with contextlib.nested( - mock.patch.object(self.agent.plugin_rpc, 'tunnel_sync', - return_value=fake_tunnel_details), - mock.patch.object(self.agent, 'setup_tunnel_port') - ) as (tunnel_sync_rpc_fn, setup_tunnel_port_fn): - self.agent.tunnel_sync() - setup_tunnel_port_fn.assert_called_once_with( - tun_name, tunnel_ip, self.agent.tunnel_types[0]) - - def test_tunnel_update(self): - tunnel_ip = '10.10.10.10' - self.agent.tunnel_types = ['gre'] - tun_name = self._create_tunnel_port_name(tunnel_ip, - self.agent.tunnel_types[0]) - kwargs = {'tunnel_ip': tunnel_ip, - 'tunnel_type': self.agent.tunnel_types[0]} - self.agent.setup_tunnel_port = mock.Mock() - self.agent.enable_tunneling = True - self.agent.l2_pop = False - self.agent.tunnel_update(context=None, **kwargs) - expected_calls = [mock.call(tun_name, tunnel_ip, - self.agent.tunnel_types[0])] - self.agent.setup_tunnel_port.assert_has_calls(expected_calls) + tunnel_sync_rpc_fn.assert_called_once_with( + self.agent.context, + self.agent.local_ip, + self.agent.tunnel_types[0]) def test__provision_local_vlan_inbound_for_tunnel(self): with mock.patch.object(self.agent, 'ryu_send_msg') as sendmsg: diff --git a/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py b/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py index 771a09ddf..5aa43f0e2 100644 --- a/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py @@ -934,7 +934,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): return_value='6'), mock.patch.object(self.agent.tun_br, "add_flow") ) as (add_tun_port_fn, add_flow_fn): - self.agent.setup_tunnel_port('portname', '1.2.3.4', 'vxlan') + self.agent._setup_tunnel_port('portname', '1.2.3.4', 'vxlan') self.assertTrue(add_tun_port_fn.called) def test_port_unbound(self): @@ -978,7 +978,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): 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, '_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) @@ -1018,7 +1018,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): 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'), + 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) @@ -1088,7 +1088,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): 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') + 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) @@ -1201,13 +1201,13 @@ class TestOvsNeutronAgent(base.BaseTestCase): constants.DEFAULT_OVSDBMON_RESPAWN) mock_loop.assert_called_once_with(polling_manager=mock.ANY) - def test_setup_tunnel_port_error_negative(self): + def test__setup_tunnel_port_error_negative(self): with contextlib.nested( mock.patch.object(self.agent.tun_br, 'add_tunnel_port', return_value='-1'), mock.patch.object(ovs_neutron_agent.LOG, 'error') ) as (add_tunnel_port_fn, log_error_fn): - ofport = self.agent.setup_tunnel_port( + ofport = self.agent._setup_tunnel_port( 'gre-1', 'remote_ip', p_const.TYPE_GRE) add_tunnel_port_fn.assert_called_once_with( 'gre-1', 'remote_ip', self.agent.local_ip, p_const.TYPE_GRE, @@ -1217,14 +1217,14 @@ class TestOvsNeutronAgent(base.BaseTestCase): {'type': p_const.TYPE_GRE, 'ip': 'remote_ip'}) self.assertEqual(ofport, 0) - def test_setup_tunnel_port_error_not_int(self): + def test__setup_tunnel_port_error_not_int(self): with contextlib.nested( mock.patch.object(self.agent.tun_br, 'add_tunnel_port', return_value=None), mock.patch.object(ovs_neutron_agent.LOG, 'exception'), mock.patch.object(ovs_neutron_agent.LOG, 'error') ) as (add_tunnel_port_fn, log_exc_fn, log_error_fn): - ofport = self.agent.setup_tunnel_port( + ofport = self.agent._setup_tunnel_port( 'gre-1', 'remote_ip', p_const.TYPE_GRE) add_tunnel_port_fn.assert_called_once_with( 'gre-1', 'remote_ip', self.agent.local_ip, p_const.TYPE_GRE, @@ -1237,14 +1237,14 @@ class TestOvsNeutronAgent(base.BaseTestCase): {'type': p_const.TYPE_GRE, 'ip': 'remote_ip'}) self.assertEqual(ofport, 0) - def test_setup_tunnel_port_error_negative_df_disabled(self): + def test__setup_tunnel_port_error_negative_df_disabled(self): with contextlib.nested( mock.patch.object(self.agent.tun_br, 'add_tunnel_port', return_value='-1'), mock.patch.object(ovs_neutron_agent.LOG, 'error') ) as (add_tunnel_port_fn, log_error_fn): self.agent.dont_fragment = False - ofport = self.agent.setup_tunnel_port( + ofport = self.agent._setup_tunnel_port( 'gre-1', 'remote_ip', p_const.TYPE_GRE) add_tunnel_port_fn.assert_called_once_with( 'gre-1', 'remote_ip', self.agent.local_ip, p_const.TYPE_GRE, @@ -1260,25 +1260,25 @@ class TestOvsNeutronAgent(base.BaseTestCase): with contextlib.nested( mock.patch.object(self.agent.plugin_rpc, 'tunnel_sync', return_value=fake_tunnel_details), - mock.patch.object(self.agent, 'setup_tunnel_port') - ) as (tunnel_sync_rpc_fn, setup_tunnel_port_fn): + mock.patch.object(self.agent, '_setup_tunnel_port') + ) as (tunnel_sync_rpc_fn, _setup_tunnel_port_fn): self.agent.tunnel_types = ['gre'] self.agent.tunnel_sync() expected_calls = [mock.call('gre-42', '100.101.102.103', 'gre')] - setup_tunnel_port_fn.assert_has_calls(expected_calls) + _setup_tunnel_port_fn.assert_has_calls(expected_calls) def test_tunnel_sync_with_ml2_plugin(self): fake_tunnel_details = {'tunnels': [{'ip_address': '100.101.31.15'}]} with contextlib.nested( mock.patch.object(self.agent.plugin_rpc, 'tunnel_sync', return_value=fake_tunnel_details), - mock.patch.object(self.agent, 'setup_tunnel_port') - ) as (tunnel_sync_rpc_fn, setup_tunnel_port_fn): + mock.patch.object(self.agent, '_setup_tunnel_port') + ) as (tunnel_sync_rpc_fn, _setup_tunnel_port_fn): self.agent.tunnel_types = ['vxlan'] self.agent.tunnel_sync() expected_calls = [mock.call('vxlan-64651f0f', '100.101.31.15', 'vxlan')] - setup_tunnel_port_fn.assert_has_calls(expected_calls) + _setup_tunnel_port_fn.assert_has_calls(expected_calls) def test_tunnel_sync_invalid_ip_address(self): fake_tunnel_details = {'tunnels': [{'ip_address': '300.300.300.300'}, @@ -1286,24 +1286,24 @@ class TestOvsNeutronAgent(base.BaseTestCase): with contextlib.nested( mock.patch.object(self.agent.plugin_rpc, 'tunnel_sync', return_value=fake_tunnel_details), - mock.patch.object(self.agent, 'setup_tunnel_port') - ) as (tunnel_sync_rpc_fn, setup_tunnel_port_fn): + mock.patch.object(self.agent, '_setup_tunnel_port') + ) as (tunnel_sync_rpc_fn, _setup_tunnel_port_fn): self.agent.tunnel_types = ['vxlan'] self.agent.tunnel_sync() - setup_tunnel_port_fn.assert_called_once_with('vxlan-64646464', - '100.100.100.100', - 'vxlan') + _setup_tunnel_port_fn.assert_called_once_with('vxlan-64646464', + '100.100.100.100', + 'vxlan') def test_tunnel_update(self): kwargs = {'tunnel_ip': '10.10.10.10', 'tunnel_type': 'gre'} - self.agent.setup_tunnel_port = mock.Mock() + self.agent._setup_tunnel_port = mock.Mock() self.agent.enable_tunneling = True self.agent.tunnel_types = ['gre'] self.agent.l2_pop = False self.agent.tunnel_update(context=None, **kwargs) expected_calls = [mock.call('gre-0a0a0a0a', '10.10.10.10', 'gre')] - self.agent.setup_tunnel_port.assert_has_calls(expected_calls) + self.agent._setup_tunnel_port.assert_has_calls(expected_calls) def test_ovs_restart(self): reply2 = {'current': set(['tap0']), -- 2.45.2