From 6ec0bc70a7907ab2d83d1dfe0b177ddf17c79d61 Mon Sep 17 00:00:00 2001 From: Sahid Orentino Ferdjaoui Date: Wed, 27 Apr 2022 09:03:55 +0200 Subject: [PATCH] ovs: make vlanmanager to handle more vlan mapping per network This change is updating the vlanmanager data structure to handle for a given network more than one vlan mapping. This is a prerequisite work needed to progress on accepting several segments per network per host. The work done here is trying to avoid changing logic in the current implementation. Unit test should not have value updated, but probably signatures changed. Partial-Bug: #1956435 Partial-Bug: #1764738 Signed-off-by: Sahid Orentino Ferdjaoui Change-Id: Ic3c147136549b17aea0fe78e930a41a5b33ab9d8 --- .../l2pop/rpc_manager/l2population_rpc.py | 32 ++-- .../openvswitch/agent/ovs_neutron_agent.py | 157 +++++++++++------- .../drivers/openvswitch/agent/vlanmanager.py | 80 ++++++--- .../rpc_manager/l2population_rpc_base.py | 3 +- .../rpc_manager/test_l2population_rpc.py | 2 +- .../agent/test_ovs_neutron_agent.py | 126 ++++++++------ .../openvswitch/agent/test_ovs_tunnel.py | 8 +- .../openvswitch/agent/test_vlanmanager.py | 41 +++-- 8 files changed, 278 insertions(+), 171 deletions(-) diff --git a/neutron/plugins/ml2/drivers/l2pop/rpc_manager/l2population_rpc.py b/neutron/plugins/ml2/drivers/l2pop/rpc_manager/l2population_rpc.py index 60a1de35394..c14dfee0c22 100644 --- a/neutron/plugins/ml2/drivers/l2pop/rpc_manager/l2population_rpc.py +++ b/neutron/plugins/ml2/drivers/l2pop/rpc_manager/l2population_rpc.py @@ -232,7 +232,8 @@ class L2populationRpcCallBackTunnelMixin(L2populationRpcCallBackMixin, vlan_manager = vlanmanager.LocalVlanManager() for network_id, values in fdb_entries.items(): try: - lvm = vlan_manager.get(network_id) + lvm = vlan_manager.get( + network_id, values.get('segment_id')) except vlanmanager.MappingNotFound: continue agent_ports = values.get('ports') @@ -307,22 +308,23 @@ class L2populationRpcCallBackTunnelMixin(L2populationRpcCallBackMixin, vlan_manager = vlanmanager.LocalVlanManager() for network_id, agent_ports in fdb_entries.items(): try: - lvm = vlan_manager.get(network_id) + lvms = vlan_manager.get_segments(network_id) except vlanmanager.MappingNotFound: continue - for agent_ip, state in agent_ports.items(): - if agent_ip == local_ip: - continue + for lvm in lvms.values(): + for agent_ip, state in agent_ports.items(): + if agent_ip == local_ip: + continue - after = state.get('after', []) - for mac_ip in after: - self.setup_entry_for_arp_reply(br, 'add', lvm.vlan, - mac_ip.mac_address, - mac_ip.ip_address) + after = state.get('after', []) + for mac_ip in after: + self.setup_entry_for_arp_reply(br, 'add', lvm.vlan, + mac_ip.mac_address, + mac_ip.ip_address) - before = state.get('before', []) - for mac_ip in before: - self.setup_entry_for_arp_reply(br, 'remove', lvm.vlan, - mac_ip.mac_address, - mac_ip.ip_address) + before = state.get('before', []) + for mac_ip in before: + self.setup_entry_for_arp_reply(br, 'remove', lvm.vlan, + mac_ip.mac_address, + mac_ip.ip_address) diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py index ca94a09488c..2fe727bf167 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -490,15 +490,20 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, return try: - lvm = self.vlan_manager.get(network['id']) - except vlanmanager.MappingNotFound: + segmentation_id_old, lvm = ( + self.vlan_manager.update_segmentation_id( + network['id'], network[provider_net.SEGMENTATION_ID])) + except vlanmanager.NotUniqMapping: + # There is a design issue, the RPC update network should not accept + # to update the segmentation id. We still support it if only one + # segment per network. + LOG.warning("Can't update segmentation id on no uniq segment " + "for a network %s", network['id']) return - segmentation_id_old = lvm.segmentation_id - if segmentation_id_old == network[provider_net.SEGMENTATION_ID]: + if segmentation_id_old is None: + # The segmentation id did not changed. return - self.vlan_manager.update_segmentation_id( - network['id'], network[provider_net.SEGMENTATION_ID]) lvid = lvm.vlan physical_network = network[provider_net.PHYSICAL_NETWORK] @@ -716,9 +721,9 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, def _add_network_ports(self, network_id, segmentation_id, port_id): self.network_ports[network_id][segmentation_id].add(port_id) - def _get_net_local_vlan_or_none(self, net_id): + def _get_net_local_vlan_or_none(self, net_id, segmentation_id): try: - return self.vlan_manager.get(net_id).vlan + return self.vlan_manager.get(net_id, segmentation_id).vlan except vlanmanager.MappingNotFound: return None @@ -982,22 +987,25 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, tags available. """ try: - lvm = self.vlan_manager.get(net_uuid) + lvm = self.vlan_manager.get(net_uuid, segmentation_id) except vlanmanager.MappingNotFound: lvid = self._local_vlan_hints.pop(net_uuid, None) if lvid is None: if not self.available_local_vlans: - LOG.error("No local VLAN available for net-id=%s", - net_uuid) + LOG.error("No local VLAN available for net-id=%s, " + "seg-id=%s", + net_uuid, segmentation_id) return lvid = self.available_local_vlans.pop() self.vlan_manager.add( net_uuid, lvid, network_type, physical_network, segmentation_id) - lvm = self.vlan_manager.get(net_uuid) + lvm = self.vlan_manager.get(net_uuid, segmentation_id) LOG.info( - "Assigning %(vlan_id)s as local vlan for net-id=%(net_uuid)s", - {'vlan_id': lvm.vlan, 'net_uuid': net_uuid}) + "Assigning %(vlan_id)s as local vlan for net-id=%(net_uuid)s, " + "seg-id=%(seg_id)s", + {'vlan_id': lvm.vlan, 'net_uuid': net_uuid, + 'seg_id': segmentation_id}) return lvm @@ -1067,20 +1075,23 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, {'network_type': network_type, 'net_uuid': net_uuid}) - def reclaim_local_vlan(self, net_uuid): + def reclaim_local_vlan(self, net_uuid, segmentation_id): '''Reclaim a local VLAN. :param net_uuid: the network uuid associated with this vlan. ''' try: - lvm = vlanmanager.LocalVlanManager().pop(net_uuid) + lvm = vlanmanager.LocalVlanManager().pop( + net_uuid, segmentation_id) except vlanmanager.MappingNotFound: - LOG.debug("Network %s not used on agent.", net_uuid) + LOG.debug("Network %s, for segmentation %s, not used on agent.", + net_uuid, segmentation_id) return LOG.info("Reclaiming vlan = %(vlan_id)s from " - "net-id = %(net_uuid)s", - {'vlan_id': lvm.vlan, 'net_uuid': net_uuid}) + "net-id = %(net_uuid)s, seg-id = %(seg_id)s", + {'vlan_id': lvm.vlan, 'net_uuid': net_uuid, + 'seg_id': segmentation_id}) if lvm.network_type in ovs_const.TUNNEL_NETWORK_TYPES: if self.enable_tunneling: @@ -1158,10 +1169,15 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, restart or recreated physical bridges and requires to do local vlan provisioning ''' - if net_uuid not in self.vlan_manager or provisioning_needed: + try: + lvm = self.vlan_manager.get(net_uuid, segmentation_id) + except vlanmanager.MappingNotFound: + lvm = None + if lvm is None or provisioning_needed: self.provision_local_vlan(net_uuid, network_type, physical_network, segmentation_id) - lvm = self.vlan_manager.get(net_uuid) + lvm = self.vlan_manager.get(net_uuid, segmentation_id) + lvm.vif_ports[port.vif_id] = port self.dvr_agent.bind_port_to_dvr(port, lvm, @@ -1201,7 +1217,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, tags_by_name = {x['name']: x['tag'] for x in port_info} for port_detail in need_binding_ports: try: - lvm = self.vlan_manager.get(port_detail['network_id']) + lvm = self.vlan_manager.get(port_detail['network_id'], + port_detail['segmentation_id']) except vlanmanager.MappingNotFound: # network for port was deleted. skip this port since it # will need to be handled as a DEAD port in the next scan @@ -1322,13 +1339,21 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, def _get_port_lvm_and_vif(self, vif_id, net_uuid=None): try: - net_uuid = net_uuid or self.vlan_manager.get_net_uuid(vif_id) + net_uuid, segmentation_id = ( + self.vlan_manager.get_net_and_segmentation_id( + vif_id, net_uuid=net_uuid)) + lvm = self.vlan_manager.get(net_uuid, segmentation_id) except vlanmanager.VifIdNotFound: LOG.info('net_uuid %s not managed by VLAN manager', net_uuid) - return None, None, None - - lvm = self.vlan_manager.get(net_uuid) + if net_uuid: + # TODO(sahid); This needs to be fixed. It supposes a segment + # per network per host. Basically this code is to avoid + # changing logic which is not the aim of this commit. + segs = self.vlan_manager.get_segments(net_uuid) + lvm = self.vlan_manager.get(net_uuid, list(segs.keys())[0]) + else: + return None, None, None if vif_id in lvm.vif_ports: vif_port = lvm.vif_ports[vif_id] @@ -1352,9 +1377,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, if vif_port and vif_id in lvm.vif_ports: self.dvr_agent.unbind_port_from_dvr(vif_port, lvm) lvm.vif_ports.pop(vif_id, None) - if not lvm.vif_ports: - self.reclaim_local_vlan(net_uuid) + self.reclaim_local_vlan(net_uuid, lvm.segmentation_id) def port_alive(self, port, log_errors=True): cur_tag = self.int_br.db_get_val("Port", port.port_name, "tag", @@ -1811,21 +1835,23 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, """ port_tags = self.int_br.get_port_tag_dict() changed_ports = set() - for lvm in self.vlan_manager: - for port in lvm.vif_ports.values(): - if ( - port.port_name in port_tags and - port_tags[port.port_name] != lvm.vlan - ): - LOG.info( - "Port '%(port_name)s' has lost " - "its vlan tag '%(vlan_tag)d'! " - "Current vlan tag on this port is '%(new_vlan_tag)s'.", - {'port_name': port.port_name, - 'vlan_tag': lvm.vlan, - 'new_vlan_tag': port_tags[port.port_name]} - ) - changed_ports.add(port.vif_id) + for vlan_mappings in self.vlan_manager: + for lvm in vlan_mappings.values(): + for port in lvm.vif_ports.values(): + if ( + port.port_name in port_tags and + port_tags[port.port_name] != lvm.vlan + ): + LOG.info( + "Port '%(port_name)s' has lost " + "its vlan tag '%(vlan_tag)d'! " + "Current vlan tag on this port is " + "'%(new_vlan_tag)s'.", + {'port_name': port.port_name, + 'vlan_tag': lvm.vlan, + 'new_vlan_tag': port_tags[port.port_name]} + ) + changed_ports.add(port.vif_id) if changed_ports: # explicitly mark these DOWN on the server since they have been # manipulated (likely a nova unplug/replug) and need to be rewired @@ -1906,11 +1932,12 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, ofports = self.tun_br_ofports[tunnel_type].values() if ofports and not self.l2_pop: # Update flooding flows to include the new tunnel - for vlan_mapping in self.vlan_manager: - if vlan_mapping.network_type == tunnel_type: - br.install_flood_to_tun(vlan_mapping.vlan, - vlan_mapping.segmentation_id, - ofports) + for vlan_mappings in self.vlan_manager: + for vlan_mapping in vlan_mappings.values(): + if vlan_mapping.network_type == tunnel_type: + br.install_flood_to_tun(vlan_mapping.vlan, + vlan_mapping.segmentation_id, + ofports) def setup_tunnel_port(self, br, remote_ip, network_type): port_name = self.get_tunnel_name( @@ -1926,19 +1953,21 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, def cleanup_tunnel_port(self, br, tun_ofport, tunnel_type): # Check if this tunnel port is still used - for lvm in self.vlan_manager: - if tun_ofport in lvm.tun_ofports: - break + for vlan_mappings in self.vlan_manager: + for lvm in vlan_mappings.values(): + if tun_ofport in lvm.tun_ofports: + # still used + return # If not, remove it - else: - items = list(self.tun_br_ofports[tunnel_type].items()) - for remote_ip, ofport in items: - if ofport == tun_ofport: - port_name = self.get_tunnel_name( - tunnel_type, self.local_ip, remote_ip) - br.delete_port(port_name) - br.cleanup_tunnel_port(ofport) - self.tun_br_ofports[tunnel_type].pop(remote_ip, None) + items = list(self.tun_br_ofports[tunnel_type].items()) + for remote_ip, ofport in items: + if ofport == tun_ofport: + port_name = self.get_tunnel_name( + tunnel_type, self.local_ip, remote_ip) + br.delete_port(port_name) + br.cleanup_tunnel_port(ofport) + self.tun_br_ofports[tunnel_type].pop( + remote_ip, None) def treat_devices_added_or_updated(self, devices, provisioning_needed, re_added): @@ -1984,7 +2013,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, if 'port_id' in details: details['vif_port'] = port details['local_vlan'] = self._get_net_local_vlan_or_none( - details['network_id']) + details['network_id'], details['segmentation_id']) LOG.info("Port %(device)s updated. Details: %(details)s", {'device': device, 'details': details}) need_binding = self.treat_vif_port(port, details['port_id'], @@ -2233,7 +2262,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, if self.enable_local_ips and port['device_owner'].startswith( n_const.DEVICE_OWNER_COMPUTE_PREFIX): try: - lvm = self.vlan_manager.get(port['network_id']) + lvm = self.vlan_manager.get( + port['network_id'], port['segmentation_id']) self.int_br.setup_local_egress_flows( port['vif_port'].ofport, lvm.vlan) except Exception as err: @@ -2250,7 +2280,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin, port['port_id'], err) def install_accepted_egress_direct_flow(self, port_detail, br_int): - lvm = self.vlan_manager.get(port_detail['network_id']) + lvm = self.vlan_manager.get( + port_detail['network_id'], port_detail['segmentation_id']) port = port_detail['vif_port'] # Adding the local vlan to register 6 in case of MAC overlapping diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/vlanmanager.py b/neutron/plugins/ml2/drivers/openvswitch/agent/vlanmanager.py index 15317670755..08d2a79ef62 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/vlanmanager.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/vlanmanager.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +from collections import defaultdict + from neutron_lib import exceptions from neutron._i18n import _ @@ -24,11 +26,17 @@ class VifIdNotFound(exceptions.NeutronException): class MappingAlreadyExists(exceptions.NeutronException): - message = _('VLAN mapping for network with id %(net_id)s already exists') + message = _('VLAN mapping for network with id %(net_id)s and ' + 'segmentation id %(seg_id)s already exists') class MappingNotFound(exceptions.NeutronException): - message = _('Mapping for network %(net_id)s not found.') + message = _('Mapping VLAN for network %(net_id)s with segmentation id ' + '%(seg_id)s not found.') + + +class NotUniqMapping(exceptions.NeutronException): + message = _('Mapping VLAN for network %(net_id)s should be unique.') class LocalVLANMapping(object): @@ -71,7 +79,7 @@ class LocalVlanManager(object): def __init__(self): if not hasattr(self, 'mapping'): - self.mapping = {} + self.mapping = defaultdict(dict) def __contains__(self, key): return key in self.mapping @@ -86,31 +94,55 @@ class LocalVlanManager(object): def add(self, net_id, vlan, network_type, physical_network, segmentation_id, vif_ports=None): - if net_id in self.mapping: - raise MappingAlreadyExists(net_id=net_id) - self.mapping[net_id] = LocalVLANMapping( + try: + if self.get(net_id, segmentation_id): + raise MappingAlreadyExists( + net_id=net_id, seg_id=segmentation_id) + except MappingNotFound: + pass + self.mapping[net_id][segmentation_id] = LocalVLANMapping( vlan, network_type, physical_network, segmentation_id, vif_ports) - def get_net_uuid(self, vif_id): - for network_id, vlan_mapping in self.mapping.items(): - if vif_id in vlan_mapping.vif_ports: - return network_id + def get_net_and_segmentation_id(self, vif_id, net_uuid=None): + # TODO(sahid): We should improve algorithm if net_uuid is passed. + for network_id, vlan_mappings in self.mapping.items(): + for segmentation_id, vlan_mapping in vlan_mappings.items(): + if vif_id in vlan_mapping.vif_ports: + return network_id, segmentation_id raise VifIdNotFound(vif_id=vif_id) - def get(self, net_id): - try: - return self.mapping[net_id] - except KeyError: - raise MappingNotFound(net_id=net_id) + def get(self, net_id, segmentation_id): + if net_id in self.mapping and segmentation_id in self.mapping[net_id]: + return self.mapping[net_id][segmentation_id] + raise MappingNotFound(net_id=net_id, seg_id=segmentation_id) - def pop(self, net_id): - try: - return self.mapping.pop(net_id) - except KeyError: - raise MappingNotFound(net_id=net_id) + def get_segments(self, net_id): + if net_id not in self.mapping: + raise MappingNotFound(net_id=net_id, seg_id="") + return self.mapping[net_id] + + def pop(self, net_id, segmentation_id): + if self.get(net_id, segmentation_id): + ret = self.mapping[net_id].pop(segmentation_id) + # if it's the last seg id for a network, let's removed the network + # entry as-well. + if len(self.mapping[net_id]) == 0: + del self.mapping[net_id] + return ret def update_segmentation_id(self, net_id, segmentation_id): - try: - self.mapping[net_id].segmentation_id = segmentation_id - except KeyError: - raise MappingNotFound(net_id=net_id) + """Returns tuple with segmentation id, lvm in success or None, None""" + if len(self.get_segments(net_id)) != 1: + # Update of segmentation id can work only if network has one + # segment. This is a design issue that should be fixed in + # future. We should not accept segmentation update for a network. + raise NotUniqMapping(net_id=net_id) + mapping = list(self.mapping[net_id].values())[0] + if mapping.segmentation_id == segmentation_id: + # No need to update + return None, None + old = mapping.segmentation_id + del self.mapping[net_id][old] + mapping.segmentation_id = segmentation_id + self.mapping[net_id][segmentation_id] = mapping + return old, mapping diff --git a/neutron/tests/unit/plugins/ml2/drivers/l2pop/rpc_manager/l2population_rpc_base.py b/neutron/tests/unit/plugins/ml2/drivers/l2pop/rpc_manager/l2population_rpc_base.py index 4955f4967d4..3d3ef6ad920 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/l2pop/rpc_manager/l2population_rpc_base.py +++ b/neutron/tests/unit/plugins/ml2/drivers/l2pop/rpc_manager/l2population_rpc_base.py @@ -120,7 +120,8 @@ class TestL2populationRpcCallBackTunnelMixinBase(base.BaseTestCase): self.lvms[i].vlan, self.type_gre, self.lvms[i].phys, self.lvms[i].segid, {self.lvms[i].vif: self.lvms[i].port}) setattr(self, 'lvm%d' % i, - self.vlan_manager.get(self.lvms[i].net)) + self.vlan_manager.get( + self.lvms[i].net, self.lvms[i].segid)) self.upd_fdb_entry1_val = { self.lvms[0].net: { diff --git a/neutron/tests/unit/plugins/ml2/drivers/l2pop/rpc_manager/test_l2population_rpc.py b/neutron/tests/unit/plugins/ml2/drivers/l2pop/rpc_manager/test_l2population_rpc.py index 99437fd0504..fe8aa4d5da0 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/l2pop/rpc_manager/test_l2population_rpc.py +++ b/neutron/tests/unit/plugins/ml2/drivers/l2pop/rpc_manager/test_l2population_rpc.py @@ -33,7 +33,7 @@ class TestL2populationRpcCallBackTunnelMixin( def test_get_agent_ports_non_existence_key_in_lvm(self): results = {} - self.vlan_manager.pop(self.lvms[1].net) + self.vlan_manager.pop(self.lvms[1].net, self.lvms[1].segid) for lvm, agent_ports in self.fakeagent.get_agent_ports( self.fdb_entries1): results[lvm] = agent_ports diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py index 1babe78ff0e..9c2be86d4db 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py @@ -69,8 +69,10 @@ TEST_PORT_ID3 = 'port-id-3' TEST_NETWORK_ID1 = 'net-id-1' TEST_NETWORK_ID2 = 'net-id-2' -TEST_SEGMENTATION_ID1 = 'seg-id-1' -TEST_SEGMENTATION_ID2 = 'seg-id-2' +# Currently it's only accepted to have one segmentation per network. In future +# we will accept more. +TEST_SEG_NET1_ID1 = 'net-id-1-seg-id-1' +TEST_SEG_NET2_ID1 = 'net-id-2-seg-id-1' TEST_MTU = 7824 @@ -189,7 +191,7 @@ class TestOvsNeutronAgent(object): int_br.db_get_val.return_value = db_get_val int_br.set_db_attribute.return_value = True needs_binding = self.agent.port_bound( - port, net_uuid, 'local', None, None, + port, net_uuid, 'local', None, 'seg1', fixed_ips, DEVICE_OWNER_COMPUTE, False) if db_get_val is None: int_br.assert_not_called() @@ -198,6 +200,7 @@ class TestOvsNeutronAgent(object): vlan_mapping = {'net_uuid': net_uuid, 'network_type': 'local', 'physical_network': 'None', + 'segmentation_id': 'seg1', 'tag': str(new_local_vlan)} set_vlan.assert_called_once_with(port, new_local_vlan) int_br.set_db_attribute.assert_called_once_with( @@ -700,7 +703,7 @@ class TestOvsNeutronAgent(object): def test_bind_devices(self): devices_up = ['tap1'] devices_down = ['tap2'] - self.agent.vlan_manager.mapping["net1"] = mock.Mock() + self.agent.vlan_manager.mapping["net1"]['seg1'] = mock.Mock() ovs_db_list = [{'name': 'tap1', 'tag': []}, {'name': 'tap2', 'tag': []}] vif_port1 = mock.Mock() @@ -709,10 +712,12 @@ class TestOvsNeutronAgent(object): vif_port2.port_name = 'tap2' port_details = [ {'network_id': 'net1', 'vif_port': vif_port1, + 'segmentation_id': 'seg1', 'device': devices_up[0], 'device_owner': 'network:dhcp', 'admin_state_up': True}, {'network_id': 'net1', 'vif_port': vif_port2, + 'segmentation_id': 'seg1', 'device': devices_down[0], 'device_owner': 'network:dhcp', 'admin_state_up': False}] @@ -737,11 +742,13 @@ class TestOvsNeutronAgent(object): self.agent.vlan_manager.add('fake_network', 1, n_const.TYPE_VXLAN, None, 1) ovs_db_list = [{'name': 'fake_device', 'tag': []}] - self.agent.vlan_manager.get('fake_network').tun_ofports = tun_ofports + self.agent.vlan_manager.get( + 'fake_network', 1).tun_ofports = tun_ofports vif_port = mock.Mock() vif_port.port_name = 'fake_device' vif_port.ofport = 1 need_binding_ports = [{'network_id': 'fake_network', + 'segmentation_id': 1, 'vif_port': vif_port, 'device': 'fake_device', 'admin_state_up': True}] @@ -774,6 +781,7 @@ class TestOvsNeutronAgent(object): vif_port.port_name = 'fake_device' vif_port.ofport = 1 need_binding_ports = [{'network_id': 'fake_network', + 'segmentation_id': 1, 'vif_port': vif_port, 'device': 'fake_device', 'admin_state_up': True}] @@ -1093,6 +1101,7 @@ class TestOvsNeutronAgent(object): vif_port = mock.Mock() vif_port.name.return_value = 'port' self.agent._bind_devices([{'network_id': 'non-existent', + 'segmentation_id': 'seg1', 'vif_port': vif_port}]) def _test_process_network_ports(self, port_info, skipped_devices=None, @@ -1398,7 +1407,7 @@ class TestOvsNeutronAgent(object): def test_process_deleted_ports_cleans_network_ports(self): self.agent._update_port_network( - TEST_PORT_ID1, TEST_NETWORK_ID1, TEST_SEGMENTATION_ID1) + TEST_PORT_ID1, TEST_NETWORK_ID1, TEST_SEG_NET1_ID1) self.agent.port_delete(context=None, port_id=TEST_PORT_ID1) self.agent.sg_agent = mock.Mock() self.agent.int_br = mock.Mock() @@ -1411,7 +1420,7 @@ class TestOvsNeutronAgent(object): self.agent.process_deleted_ports(port_info={}) self.assertEqual( set(), self.agent.network_ports[ - TEST_NETWORK_ID1][TEST_SEGMENTATION_ID1]) + TEST_NETWORK_ID1][TEST_SEG_NET1_ID1]) def test_network_update(self): """Network update marks port for update. """ @@ -1419,7 +1428,7 @@ class TestOvsNeutronAgent(object): port = {'id': TEST_PORT_ID1, 'network_id': network['id']} self.agent._update_port_network( - port['id'], port['network_id'], TEST_SEGMENTATION_ID1) + port['id'], port['network_id'], TEST_SEG_NET1_ID1) with mock.patch.object(self.agent.plugin_rpc, 'get_network_details'), \ mock.patch.object(self.agent, '_update_network_segmentation_id'): @@ -1436,7 +1445,7 @@ class TestOvsNeutronAgent(object): port = {'id': TEST_PORT_ID1, 'network_id': network['id']} self.agent._update_port_network( - port['id'], port['network_id'], TEST_SEGMENTATION_ID1) + port['id'], port['network_id'], TEST_SEG_NET1_ID1) self.agent.port_delete(context=None, port_id=port['id']) with mock.patch.object(self.agent.plugin_rpc, 'get_network_details'), \ mock.patch.object(self.agent, @@ -1447,20 +1456,20 @@ class TestOvsNeutronAgent(object): def test_update_port_network(self): """Ensure ports are associated and moved across networks correctly.""" self.agent._update_port_network( - TEST_PORT_ID1, TEST_NETWORK_ID1, TEST_SEGMENTATION_ID1) + TEST_PORT_ID1, TEST_NETWORK_ID1, TEST_SEG_NET1_ID1) self.agent._update_port_network( - TEST_PORT_ID2, TEST_NETWORK_ID1, TEST_SEGMENTATION_ID2) + TEST_PORT_ID2, TEST_NETWORK_ID1, TEST_SEG_NET1_ID1) self.agent._update_port_network( - TEST_PORT_ID3, TEST_NETWORK_ID2, TEST_SEGMENTATION_ID1) + TEST_PORT_ID3, TEST_NETWORK_ID2, TEST_SEG_NET2_ID1) self.agent._update_port_network( - TEST_PORT_ID1, TEST_NETWORK_ID2, TEST_SEGMENTATION_ID1) + TEST_PORT_ID1, TEST_NETWORK_ID2, TEST_SEG_NET2_ID1) self.assertEqual(set([TEST_PORT_ID2]), self.agent.network_ports[TEST_NETWORK_ID1][ - TEST_SEGMENTATION_ID2]) + TEST_SEG_NET1_ID1]) self.assertEqual(set([TEST_PORT_ID1, TEST_PORT_ID3]), self.agent.network_ports[TEST_NETWORK_ID2][ - TEST_SEGMENTATION_ID1]) + TEST_SEG_NET2_ID1]) def test_port_delete(self): vif = FakeVif() @@ -1802,9 +1811,11 @@ class TestOvsNeutronAgent(object): with mock.patch.object(self.agent, "reclaim_local_vlan") as reclvl_fn: self.agent.enable_tunneling = True lvm = mock.Mock() + lvm.network_id = "netuid12345" + lvm.segmentation_id = "seg1" lvm.network_type = "gre" lvm.vif_ports = {"vif1": mock.Mock()} - self.agent.vlan_manager.mapping["netuid12345"] = lvm + self.agent.vlan_manager.mapping["netuid12345"]['seg1'] = lvm self.agent.port_unbound("vif1", "netuid12345") self.assertTrue(reclvl_fn.called) @@ -1827,7 +1838,8 @@ class TestOvsNeutronAgent(object): lvm2.vlan = 'vlan2' lvm2.segmentation_id = 'seg2' lvm2.tun_ofports = set(['1', '2']) - self.agent.vlan_manager.mapping = {'net1': lvm1, 'net2': lvm2} + self.agent.vlan_manager.mapping = {'net1': {"seg1": lvm1}, + 'net2': {"seg2": lvm2}} self.agent.tun_br_ofports = {'gre': {'1.1.1.1': '1', '2.2.2.2': '2'}} self.agent.arp_responder_enabled = True @@ -1870,7 +1882,7 @@ class TestOvsNeutronAgent(object): self._prepare_l2_pop_ofports() fdb_entry = {'net1': {'network_type': 'gre', - 'segment_id': 'tun1', + 'segment_id': 'seg1', 'ports': {'2.2.2.2': [l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP1), @@ -1895,7 +1907,7 @@ class TestOvsNeutronAgent(object): self._prepare_l2_pop_ofports() fdb_entry = {'net2': {'network_type': 'gre', - 'segment_id': 'tun2', + 'segment_id': 'seg2', 'ports': {'2.2.2.2': [l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP1), @@ -1915,7 +1927,7 @@ class TestOvsNeutronAgent(object): self._prepare_l2_pop_ofports() fdb_entry = {'net1': {'network_type': 'gre', - 'segment_id': 'tun1', + 'segment_id': 'seg1', 'ports': {'1.1.1.1': [l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP1)]}}} with mock.patch.object(self.agent, 'tun_br', autospec=True) as tun_br,\ @@ -1933,7 +1945,7 @@ class TestOvsNeutronAgent(object): self._prepare_l2_pop_ofports() fdb_entry = {'net2': {'network_type': 'gre', - 'segment_id': 'tun2', + 'segment_id': 'seg2', 'ports': {'2.2.2.2': [n_const.FLOODING_ENTRY]}}} with mock.patch.object(self.agent, 'tun_br', autospec=True) as tun_br: self.agent.fdb_remove(None, fdb_entry) @@ -1972,22 +1984,21 @@ class TestOvsNeutronAgent(object): self.agent.l2_pop = True self.agent.enable_tunneling = True with mock.patch.object(self.agent, 'tun_br', autospec=True) as tun_br: - self.agent.reclaim_local_vlan('net1') + self.agent.reclaim_local_vlan('net1', 'seg1') tun_br.cleanup_tunnel_port.assert_not_called() with mock.patch.object(self.mod_agent.LOG, 'debug') as log_debug_fn: - self.agent.reclaim_local_vlan('net999') + self.agent.reclaim_local_vlan('net999', 'seg999') log_debug_fn.assert_called_once_with( - 'Network %s not used on agent.', - 'net999', - ) + 'Network %s, for segmentation %s, not used on agent.', + 'net999', 'seg999') def test_recl_lv_port_to_remove(self): self._prepare_l2_pop_ofports() self.agent.l2_pop = True self.agent.enable_tunneling = True with mock.patch.object(self.agent, 'tun_br', autospec=True) as tun_br: - self.agent.reclaim_local_vlan('net2') + self.agent.reclaim_local_vlan('net2', 'seg2') tun_br.delete_port.assert_called_once_with('gre-02020202') def _test_ext_br_recreated(self, setup_bridges_side_effect): @@ -2671,9 +2682,10 @@ class TestOvsNeutronAgent(object): provider_net.NETWORK_TYPE: n_const.TYPE_VLAN, provider_net.PHYSICAL_NETWORK: 'default_network'} with mock.patch.object(self.agent.vlan_manager, - 'update_segmentation_id') as mock_update_segid: + 'update_segmentation_id', + return_value=(None, None)) as mock_update_segid: self.agent._update_network_segmentation_id(network) - mock_update_segid.assert_not_called() + mock_update_segid.assert_called_once_with('my-net-uuid', 1005) def test__update_network_segmentation_id_segmentation_id_not_updated(self): network = {'id': 'my-net-uuid', @@ -2683,9 +2695,10 @@ class TestOvsNeutronAgent(object): self.agent.vlan_manager.add('my-net-uuid', 5, n_const.TYPE_VLAN, 'provider_net', 1005, None) with mock.patch.object(self.agent.vlan_manager, - 'update_segmentation_id') as mock_update_segid: + 'update_segmentation_id', + return_value=(None, None)) as mock_update_segid: self.agent._update_network_segmentation_id(network) - mock_update_segid.assert_not_called() + mock_update_segid.assert_called_once_with('my-net-uuid', 1005) def test__update_network_segmentation_id_multisegments(self): network = {'id': 'my-net-uuid', @@ -3232,7 +3245,8 @@ class TestOvsDvrNeutronAgent(object): n_const.DEVICE_OWNER_DVR_INTERFACE, False) phy_ofp = self.agent.dvr_agent.phys_ofports[physical_network] int_ofp = self.agent.dvr_agent.int_ofports[physical_network] - lvid = self.agent.vlan_manager.get(self._net_uuid).vlan + lvid = self.agent.vlan_manager.get( + self._net_uuid, segmentation_id).vlan expected_on_phys_br = [ mock.call.provision_local_vlan( port=phy_ofp, @@ -3329,7 +3343,8 @@ class TestOvsDvrNeutronAgent(object): self._port, self._net_uuid, network_type, physical_network, segmentation_id, self._fixed_ips, n_const.DEVICE_OWNER_DVR_INTERFACE, False) - lvid = self.agent.vlan_manager.get(self._net_uuid).vlan + lvid = self.agent.vlan_manager.get( + self._net_uuid, self._segmentation_id).vlan expected_on_int_br = self._expected_port_bound( self._port, lvid) if ip_version == n_const.IP_VERSION_4: @@ -3418,7 +3433,8 @@ class TestOvsDvrNeutronAgent(object): def test_port_bound_for_dvr_with_csnat_ports(self): self._setup_for_dvr_test() int_br, tun_br = self._port_bound_for_dvr_with_csnat_ports() - lvid = self.agent.vlan_manager.get(self._net_uuid).vlan + lvid = self.agent.vlan_manager.get( + self._net_uuid, self._segmentation_id).vlan expected_on_int_br = [ mock.call.install_dvr_to_src_mac( network_type='vxlan', @@ -3433,7 +3449,7 @@ class TestOvsDvrNeutronAgent(object): mock.call.provision_local_vlan( network_type='vxlan', lvid=lvid, - segmentation_id=None, + segmentation_id=self._segmentation_id, distributed=True, ), ] @@ -3471,7 +3487,8 @@ class TestOvsDvrNeutronAgent(object): # simulate a replug self._port.ofport = 12 int_br, tun_br = self._port_bound_for_dvr_with_csnat_ports() - lvid = self.agent.vlan_manager.get(self._net_uuid).vlan + lvid = self.agent.vlan_manager.get( + self._net_uuid, self._segmentation_id).vlan expected_on_int_br = [ mock.call.delete_dvr_to_src_mac( network_type='vxlan', @@ -3517,7 +3534,7 @@ class TestOvsDvrNeutronAgent(object): mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br): self.agent.port_bound( self._port, self._net_uuid, 'vxlan', - None, None, self._fixed_ips, + None, self._segmentation_id, self._fixed_ips, n_const.DEVICE_OWNER_ROUTER_SNAT, False) return int_br, tun_br @@ -3537,7 +3554,7 @@ class TestOvsDvrNeutronAgent(object): mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br): self.agent.port_bound( self._port, self._net_uuid, 'vxlan', - None, None, self._fixed_ips, + None, self._segmentation_id, self._fixed_ips, n_const.DEVICE_OWNER_ROUTER_SNAT, False) int_br.install_dvr_to_src_mac.assert_not_called() @@ -3603,7 +3620,8 @@ class TestOvsDvrNeutronAgent(object): physical_network, segmentation_id, self._fixed_ips, n_const.DEVICE_OWNER_DVR_INTERFACE, False) - lvid = self.agent.vlan_manager.get(self._net_uuid).vlan + lvid = self.agent.vlan_manager.get( + self._net_uuid, self._segmentation_id).vlan # Bound non-gateway port self.agent.port_bound( @@ -3751,16 +3769,19 @@ class TestOvsDvrNeutronAgent(object): else: self.agent.port_bound( self._port, self._net_uuid, 'vxlan', - None, None, self._fixed_ips, + None, self._segmentation_id, self._fixed_ips, n_const.DEVICE_OWNER_DVR_INTERFACE, False) - lvid = self.agent.vlan_manager.get(self._net_uuid).vlan + lvid = self.agent.vlan_manager.get( + self._net_uuid, self._segmentation_id).vlan int_br.assert_has_calls( self._expected_port_bound(self._port, lvid), any_order=True) expected_on_tun_br = [ - mock.call.provision_local_vlan(network_type='vxlan', - lvid=lvid, segmentation_id=None, distributed=True), + mock.call.provision_local_vlan( + network_type='vxlan', + lvid=lvid, segmentation_id=self._segmentation_id, + distributed=True), ] + self._expected_install_dvr_process( port=self._port, lvid=lvid, @@ -3792,7 +3813,8 @@ class TestOvsDvrNeutronAgent(object): failed_devices = {'added': set(), 'removed': set()} failed_devices['removed'] = self.agent.treat_devices_removed( [self._port.vif_id]) - lvid = self.agent.vlan_manager.get(self._net_uuid).vlan + lvid = self.agent.vlan_manager.get( + self._net_uuid, self._segmentation_id).vlan if ip_version == n_const.IP_VERSION_4: expected = [ mock.call.delete_dvr_process_ipv4( @@ -3857,16 +3879,17 @@ class TestOvsDvrNeutronAgent(object): mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br): self.agent.port_bound( self._port, self._net_uuid, 'vxlan', - None, None, self._fixed_ips, + None, self._segmentation_id, self._fixed_ips, n_const.DEVICE_OWNER_DVR_INTERFACE, False) - lvid = self.agent.vlan_manager.get(self._net_uuid).vlan + lvid = self.agent.vlan_manager.get( + self._net_uuid, self._segmentation_id).vlan int_br.assert_has_calls( self._expected_port_bound(self._port, lvid), any_order=True) expected_on_tun_br = [ mock.call.provision_local_vlan( network_type='vxlan', - segmentation_id=None, + segmentation_id=self._segmentation_id, lvid=lvid, distributed=True), ] + self._expected_install_dvr_process( @@ -3879,7 +3902,7 @@ class TestOvsDvrNeutronAgent(object): tun_br.reset_mock() self.agent.port_bound(self._compute_port, self._net_uuid, 'vxlan', - None, None, + None, self._segmentation_id, self._compute_fixed_ips, device_owner, False) int_br.assert_has_calls( @@ -3958,10 +3981,11 @@ class TestOvsDvrNeutronAgent(object): mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br): self.agent.port_bound( self._port, self._net_uuid, 'vxlan', - None, None, self._fixed_ips, + None, self._segmentation_id, self._fixed_ips, n_const.DEVICE_OWNER_ROUTER_SNAT, False) - lvid = self.agent.vlan_manager.get(self._net_uuid).vlan + lvid = self.agent.vlan_manager.get( + self._net_uuid, self._segmentation_id).vlan expected_on_int_br = [ mock.call.install_dvr_to_src_mac( network_type='vxlan', @@ -3976,7 +4000,7 @@ class TestOvsDvrNeutronAgent(object): mock.call.provision_local_vlan( network_type='vxlan', lvid=lvid, - segmentation_id=None, + segmentation_id=self._segmentation_id, distributed=True, ), ] diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py index c8f2d13b183..5ee8357834f 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py @@ -455,7 +455,7 @@ class TunnelTest(object): a = self._build_agent() a.available_local_vlans = set() a.vlan_manager.add(NET_UUID, *self.LVM_DATA) - a.reclaim_local_vlan(NET_UUID) + a.reclaim_local_vlan(NET_UUID, LS_ID) self.assertIn(self.LVM_DATA[0], a.available_local_vlans) self._verify_mock_calls() @@ -475,7 +475,7 @@ class TunnelTest(object): a.available_local_vlans = set() a.vlan_manager.add(NET_UUID, *self.LVM_FLAT_DATA) - a.reclaim_local_vlan(NET_UUID) + a.reclaim_local_vlan(NET_UUID, LS_ID) self.assertIn(self.LVM_FLAT_DATA[0], a.available_local_vlans) self._verify_mock_calls() @@ -495,7 +495,7 @@ class TunnelTest(object): a.available_local_vlans = set() a.vlan_manager.add(NET_UUID, *self.LVM_VLAN_DATA) - a.reclaim_local_vlan(NET_UUID) + a.reclaim_local_vlan(NET_UUID, LS_ID) self.assertIn(self.LVM_VLAN_DATA[0], a.available_local_vlans) self._verify_mock_calls() @@ -528,7 +528,7 @@ class TunnelTest(object): a.vlan_manager.add(NET_UUID, *self.LVM_DATA) a.port_unbound(VIF_ID, NET_UUID) - reclaim_local_vlan.assert_called_once_with(NET_UUID) + reclaim_local_vlan.assert_called_once_with(NET_UUID, LS_ID) self._verify_mock_calls() def test_port_dead(self): diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_vlanmanager.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_vlanmanager.py index a4e46b3c3bd..6496cf9ff6b 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_vlanmanager.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_vlanmanager.py @@ -72,29 +72,30 @@ class TestLocalVlanManager(base.BaseTestCase): created_vlans = [] for val in range(3): self.vlan_manager.add(val, val, val, val, val) - created_vlans.append(self.vlan_manager.get(val)) + created_vlans.append({val: self.vlan_manager.get(val, val)}) self.assertCountEqual(created_vlans, list(self.vlan_manager)) - def test_get_net_uuid_existing(self): + def test_get_net_and_segmentation_id_existing(self): port_id = 'port-id' vlan_data = (2, 3, 4, 5, {port_id: 'port'}) net_id = 1 self.vlan_manager.add(net_id, *vlan_data) - obtained_net_id = self.vlan_manager.get_net_uuid(port_id) - self.assertEqual(net_id, obtained_net_id) + obtained_net_id = ( + self.vlan_manager.get_net_and_segmentation_id(port_id)) + self.assertEqual((net_id, 5), obtained_net_id) - def test_get_net_uuid_non_existing_raises_exception(self): + def test_get_net_and_segmentation_id_non_existing_raises_exception(self): vlan_data = (1, 2, 3, 4, 5, {'port_id': 'port'}) self.vlan_manager.add(*vlan_data) with testtools.ExpectedException(vlanmanager.VifIdNotFound): - self.vlan_manager.get_net_uuid('non-existing-port') + self.vlan_manager.get_net_and_segmentation_id('non-existing-port') def test_add_and_get(self): vlan_data = (2, 3, 4, 5, 6) expected_vlan_mapping = vlanmanager.LocalVLANMapping(*vlan_data) self.vlan_manager.add(1, *vlan_data) - vlan_mapping = self.vlan_manager.get(1) + vlan_mapping = self.vlan_manager.get(1, 5) self.assertEqual(expected_vlan_mapping, vlan_mapping) def test_add_existing_raises_exception(self): @@ -105,23 +106,39 @@ class TestLocalVlanManager(base.BaseTestCase): def test_get_non_existing_raises_keyerror(self): with testtools.ExpectedException(vlanmanager.MappingNotFound): - self.vlan_manager.get(1) + self.vlan_manager.get(1, 5) def test_pop(self): vlan_data = (2, 3, 4, 5, 6) expected_vlan_mapping = vlanmanager.LocalVLANMapping(*vlan_data) self.vlan_manager.add(1, *vlan_data) - vlan_mapping = self.vlan_manager.pop(1) + vlan_mapping = self.vlan_manager.pop(1, 5) self.assertEqual(expected_vlan_mapping, vlan_mapping) self.assertFalse(self.vlan_manager.mapping) def test_pop_non_existing_raises_exception(self): with testtools.ExpectedException(vlanmanager.MappingNotFound): - self.vlan_manager.pop(1) + self.vlan_manager.pop(1, 5) def test_update_segmentation_id(self): self.vlan_manager.add('net_id', 'vlan_id', 'vlan', 'phys_net', 1001, None) - self.assertEqual(1001, self.vlan_manager.get('net_id').segmentation_id) + self.assertEqual(1001, self.vlan_manager.get( + 'net_id', 1001).segmentation_id) self.vlan_manager.update_segmentation_id('net_id', 1002) - self.assertEqual(1002, self.vlan_manager.get('net_id').segmentation_id) + self.assertEqual(1002, self.vlan_manager.get( + 'net_id', 1002).segmentation_id) + + def test_update_segmentation_id_not_found(self): + with testtools.ExpectedException(vlanmanager.MappingNotFound): + self.vlan_manager.update_segmentation_id( + 'net_id-notfound', 1002) + + def test_update_segmentation_id_not_uniq(self): + self.vlan_manager.add('net_id-not-uniq', 'vlan_id', 'vlan', 'phys_net', + 1001, None) + self.vlan_manager.add('net_id-not-uniq', 'vlan_id', 'vlan', 'phys_net', + 1002, None) + with testtools.ExpectedException(vlanmanager.NotUniqMapping): + self.vlan_manager.update_segmentation_id( + 'net_id-not-uniq', 1003)