class L2ServiceBuilder(object): def __init__(self, conf, f5_global_routed_mode): self.conf = conf self.f5_global_routed_mode = f5_global_routed_mode self.vlan_binding = None self.fdb_connector = None self.vcmp_manager = None self.interface_mapping = {} self.tagging_mapping = {} self.system_helper = SystemHelper() self.network_helper = NetworkHelper() self.service_adapter = ServiceModelAdapter(conf) if not f5_global_routed_mode: self.fdb_connector = FDBConnectorML2(conf) if self.conf.vlan_binding_driver: try: self.vlan_binding = importutils.import_object( self.conf.vlan_binding_driver, self.conf, self) except ImportError: LOG.error('Failed to import VLAN binding driver: %s' % self.conf.vlan_binding_driver) # map format is phynet:interface:tagged for maps in self.conf.f5_external_physical_mappings: intmap = maps.split(':') net_key = str(intmap[0]).strip() if len(intmap) > 3: net_key = net_key + ':' + str(intmap[3]).strip() self.interface_mapping[net_key] = str(intmap[1]).strip() self.tagging_mapping[net_key] = str(intmap[2]).strip() LOG.debug('physical_network %s = interface %s, tagged %s' % (net_key, intmap[1], intmap[2])) def post_init(self): if self.vlan_binding: LOG.debug( 'Getting BIG-IP device interface for VLAN Binding') self.vlan_binding.register_bigip_interfaces() def tunnel_sync(self, tunnel_ips): if self.fdb_connector: self.fdb_connector.advertise_tunnel_ips(tunnel_ips) def set_tunnel_rpc(self, tunnel_rpc): # Provide FDB Connector with ML2 RPC access if self.fdb_connector: self.fdb_connector.set_tunnel_rpc(tunnel_rpc) def set_l2pop_rpc(self, l2pop_rpc): # Provide FDB Connector with ML2 RPC access if self.fdb_connector: self.fdb_connector.set_l2pop_rpc(l2pop_rpc) def set_context(self, context): if self.fdb_connector: self.fdb_connector.set_context(context) def is_common_network(self, network): # Does this network belong in the /Common folder? return network['shared'] or \ (network['id'] in self.conf.common_network_ids) or \ ('router:external' in network and network['router:external'] and self.conf.f5_common_external_networks) def get_vlan_name(self, network, hostname): # Construct a consistent vlan name net_key = network['provider:physical_network'] # look for host specific interface mapping if net_key + ':' + hostname in self.interface_mapping: interface = self.interface_mapping[net_key + ':' + hostname] tagged = self.tagging_mapping[net_key + ':' + hostname] # look for specific interface mapping elif net_key in self.interface_mapping: interface = self.interface_mapping[net_key] tagged = self.tagging_mapping[net_key] # use default mapping else: interface = self.interface_mapping['default'] tagged = self.tagging_mapping['default'] if tagged: vlanid = network['provider:segmentation_id'] else: vlanid = 0 vlan_name = "vlan-" + \ str(interface).replace(".", "-") + \ "-" + str(vlanid) if len(vlan_name) > 15: vlan_name = 'vlan-tr-' + str(vlanid) return vlan_name def assure_bigip_network(self, bigip, network): # Ensure bigip has configured network object if not network: LOG.error(' assure_bigip_network: ' 'Attempted to assure a network with no id..skipping.') return if network['id'] in bigip.assured_networks: return if network['id'] in self.conf.common_network_ids: LOG.debug(' assure_bigip_network: ' 'Network is a common global network... skipping.') return LOG.debug(" assure_bigip_network network: %s" % str(network)) start_time = time() if self.is_common_network(network): network_folder = 'Common' else: network_folder = self.service_adapter.get_folder_name( network['tenant_id'] ) # setup all needed L2 network segments if network['provider:network_type'] == 'flat': self._assure_device_network_flat(network, bigip, network_folder) elif network['provider:network_type'] == 'vlan': self._assure_device_network_vlan(network, bigip, network_folder) elif network['provider:network_type'] == 'vxlan': self._assure_device_network_vxlan(network, bigip, network_folder) elif network['provider:network_type'] == 'gre': self._assure_device_network_gre(network, bigip, network_folder) else: error_message = 'Unsupported network type %s.' \ % network['provider:network_type'] + \ ' Cannot setup network.' LOG.error(error_message) raise f5ex.InvalidNetworkType(error_message) bigip.assured_networks.append(network['id']) if time() - start_time > .001: LOG.debug(" assure bigip network took %.5f secs" % (time() - start_time)) def _assure_device_network_flat(self, network, bigip, network_folder): # Ensure bigip has configured flat vlan (untagged) interface = self.interface_mapping['default'] vlanid = 0 # Do we have host specific mappings? net_key = network['provider:physical_network'] if net_key + ':' + bigip.hostname in \ self.interface_mapping: interface = self.interface_mapping[ net_key + ':' + bigip.hostname] # Do we have a mapping for this network elif net_key in self.interface_mapping: interface = self.interface_mapping[net_key] vlan_name = self.get_vlan_name(network, bigip.hostname) # TODO(Rich Browne): Implementation with VCMP self._assure_vcmp_device_network(bigip, vlan={'name': vlan_name, 'folder': network_folder, 'id': vlanid, 'interface': interface, 'network': network}) if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip): interface = None model = {'name': vlan_name, 'interface': interface, 'partition': network_folder, 'description': network['id'], 'route_domain_id': network['route_domain_id']} self.network_helper.create_vlan(bigip, model) def _assure_device_network_vlan(self, network, bigip, network_folder): # Ensure bigip has configured tagged vlan # VLAN names are limited to 64 characters including # the folder name, so we name them foolish things. interface = self.interface_mapping['default'] tagged = self.tagging_mapping['default'] # Do we have host specific mappings? net_key = network['provider:physical_network'] if net_key + ':' + bigip.hostname in \ self.interface_mapping: interface = self.interface_mapping[ net_key + ':' + bigip.hostname] tagged = self.tagging_mapping[ net_key + ':' + bigip.hostname] # Do we have a mapping for this network elif net_key in self.interface_mapping: interface = self.interface_mapping[net_key] tagged = self.tagging_mapping[net_key] if tagged: vlanid = network['provider:segmentation_id'] else: vlanid = 0 vlan_name = self.get_vlan_name(network, bigip.hostname) self._assure_vcmp_device_network(bigip, vlan={'name': vlan_name, 'folder': network_folder, 'id': vlanid, 'interface': interface, 'network': network}) if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip): interface = None model = {'name': vlan_name, 'interface': interface, 'tag': vlanid, 'partition': network_folder, 'description': network['id'], 'route_domain_id': network['route_domain_id']} self.network_helper.create_vlan(bigip, model) if self.vlan_binding: self.vlan_binding.allow_vlan( device_name=bigip.device_name, interface=interface, vlanid=vlanid ) def _assure_device_network_vxlan(self, network, bigip, partition): # Ensure bigip has configured vxlan if not bigip.local_ip: error_message = 'Cannot create tunnel %s on %s' \ % (network['id'], bigip.hostname) error_message += ' no VTEP SelfIP defined.' LOG.error('VXLAN:' + error_message) raise f5ex.MissingVTEPAddress('VXLAN:' + error_message) tunnel_name = _get_tunnel_name(network) # create the main tunnel entry for the fdb records payload = {'name': tunnel_name, 'partition': partition, 'profile': 'vxlan_ovs', 'key': network['provider:segmentation_id'], 'localAddress': bigip.local_ip, 'description': network['id'], 'route_domain_id': network['route_domain_id']} LOG.debug("---Creating VXLAN network---") LOG.debug(payload) self.network_helper.create_multipoint_tunnel(bigip, payload) LOG.debug("---VXLAN network Created---") if self.fdb_connector: self.fdb_connector.notify_vtep_added(network, bigip.local_ip) def _assure_device_network_gre(self, network, bigip, partition): # Ensure bigip has configured gre tunnel if not bigip.local_ip: error_message = 'Cannot create tunnel %s on %s' \ % (network['id'], bigip.hostname) error_message += ' no VTEP SelfIP defined.' LOG.error('L2GRE:' + error_message) raise f5ex.MissingVTEPAddress('L2GRE:' + error_message) tunnel_name = _get_tunnel_name(network) payload = {'name': tunnel_name, 'partition': partition, 'profile': 'gre_ovs', 'key': network['provider:segmentation_id'], 'localAddress': bigip.local_ip, 'description': network['id'], 'route_domain_id': network['route_domain_id']} self.network_helper.create_multipoint_tunnel(bigip, payload) if self.fdb_connector: self.fdb_connector.notify_vtep_added(network, bigip.local_ip) def _is_vlan_assoc_with_vcmp_guest(self, bigip, vlan): if not self.vcmp_manager: return False # Is a vlan associated with a vcmp_guest? try: vcmp_host = self.vcmp_manager.get_vcmp_host(bigip) vcmp_guest = self.vcmp_manager.get_vcmp_guest(vcmp_host, bigip) vlan_list = vcmp_host['bigip'].system.sys_vcmp.get_vlan( [vcmp_guest['name']]) full_path_vlan_name = '/Common/' + prefixed(vlan['name']) if full_path_vlan_name in vlan_list[0]: LOG.debug(('VLAN %s is associated with guest %s' % (full_path_vlan_name, vcmp_guest['mgmt_addr']))) return True except Exception as exc: LOG.error(('Exception checking association of VLAN %s ' 'to vCMP Guest %s: %s ' % (vlan['name'], vcmp_guest['mgmt_addr'], exc))) return False LOG.debug(('VLAN %s is not associated with guest %s' % (full_path_vlan_name, vcmp_guest['mgmt_addr']))) return False def _assure_vcmp_device_network(self, bigip, vlan): # REVISIT FOR VCMP SUPPORT # For vCMP Guests, add VLAN to vCMP Host, associate VLAN with # vCMP Guest, and remove VLAN from /Common on vCMP Guest. if not self.vcmp_manager: return vcmp_host = self.vcmp_manager.get_vcmp_host(bigip) if not vcmp_host: return # Create the VLAN on the vCMP Host try: model = {'name': vlan['name'], 'partition': '/Common', 'tag': vlan['id'], 'interface': vlan['interface'], 'description': vlan['network']['id'], 'route_domain_id': vlan['network']['route_domain_id']} self.network_helper.create_vlan(bigip, model) LOG.debug(('Created VLAN %s on vCMP Host %s' % (vlan['name'], vcmp_host['bigip'].hostname))) except Exception as exc: LOG.error( ('Exception creating VLAN %s on vCMP Host %s:%s' % (vlan['name'], vcmp_host['bigip'].hostname, exc))) # Determine if the VLAN is already associated with the vCMP Guest if self._is_vlan_assoc_with_vcmp_guest(bigip, vlan): return # Associate the VLAN with the vCMP Guest # MSG: bigip.system does not exist vcmp_guest = self.vcmp_manager.get_vcmp_guest(vcmp_host, bigip) try: vlan_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\ create('Common.StringSequence') vlan_seq.values = prefixed(vlan['name']) vlan_seq_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\ create('Common.StringSequenceSequence') vlan_seq_seq.values = [vlan_seq] vcmp_host['bigip'].system.sys_vcmp.add_vlan([vcmp_guest['name']], vlan_seq_seq) LOG.debug(('Associated VLAN %s with vCMP Guest %s' % (vlan['name'], vcmp_guest['mgmt_addr']))) except Exception as exc: LOG.error(('Exception associating VLAN %s to vCMP Guest %s: %s ' % (vlan['name'], vcmp_guest['mgmt_addr'], exc))) # Wait for the VLAN to propagate to /Common on vCMP Guest full_path_vlan_name = '/Common/' + prefixed(vlan['name']) vlan_created = False v = bigip.net.vlans.vlan try: for _ in range(0, 30): if v.exists(name=vlan['name'], partition='/Common'): v.load(name=vlan['name'], partition='/Common') vlan_created = True break LOG.debug(('Wait for VLAN %s to be created on vCMP Guest %s.' % (full_path_vlan_name, vcmp_guest['mgmt_addr']))) # sleep(1) if vlan_created: LOG.debug(('VLAN %s exists on vCMP Guest %s.' % (full_path_vlan_name, vcmp_guest['mgmt_addr']))) else: LOG.error(('VLAN %s does not exist on vCMP Guest %s.' % (full_path_vlan_name, vcmp_guest['mgmt_addr']))) except Exception as exc: LOG.error(('Exception waiting for vCMP Host VLAN %s to ' 'be created on vCMP Guest %s: %s' % (vlan['name'], vcmp_guest['mgmt_addr'], exc))) # Delete the VLAN from the /Common folder on the vCMP Guest if vlan_created: try: v.delete() LOG.debug(('Deleted VLAN %s from vCMP Guest %s' % (full_path_vlan_name, vcmp_guest['mgmt_addr']))) except Exception as exc: LOG.error( ('Exception deleting VLAN %s from vCMP Guest %s: %s' % (full_path_vlan_name, vcmp_guest['mgmt_addr'], exc))) def delete_bigip_network(self, bigip, network): # Delete network on bigip if network['id'] in self.conf.common_network_ids: LOG.debug('skipping delete of common network %s' % network['id']) return if self.is_common_network(network): network_folder = 'Common' else: network_folder = self.service_adapter.get_folder_name( network['tenant_id']) if network['provider:network_type'] == 'vlan': self._delete_device_vlan(bigip, network, network_folder) elif network['provider:network_type'] == 'flat': self._delete_device_flat(bigip, network, network_folder) elif network['provider:network_type'] == 'vxlan': self._delete_device_vxlan(bigip, network, network_folder) elif network['provider:network_type'] == 'gre': self._delete_device_gre(bigip, network, network_folder) else: LOG.error('Unsupported network type %s. Can not delete.' % network['provider:network_type']) if network['id'] in bigip.assured_networks: bigip.assured_networks.remove(network['id']) def _delete_device_vlan(self, bigip, network, network_folder): # Delete tagged vlan on specific bigip vlan_name = self.get_vlan_name(network, bigip.hostname) self.network_helper.delete_vlan( bigip, vlan_name, partition=network_folder ) if self.vlan_binding: interface = self.interface_mapping['default'] tagged = self.tagging_mapping['default'] vlanid = 0 # Do we have host specific mappings? net_key = network['provider:physical_network'] if net_key + ':' + bigip.hostname in \ self.interface_mapping: interface = self.interface_mapping[ net_key + ':' + bigip.hostname] tagged = self.tagging_mapping[ net_key + ':' + bigip.hostname] # Do we have a mapping for this network elif net_key in self.interface_mapping: interface = self.interface_mapping[net_key] tagged = self.tagging_mapping[net_key] if tagged: vlanid = network['provider:segmentation_id'] else: vlanid = 0 self.vlan_binding.prune_vlan( device_name=bigip.device_name, interface=interface, vlanid=vlanid ) self._delete_vcmp_device_network(bigip, vlan_name) def _delete_device_flat(self, bigip, network, network_folder): # Delete untagged vlan on specific bigip vlan_name = self.get_vlan_name(network, bigip.hostname) self.network_helper.delete_vlan( bigip, vlan_name, partition=network_folder ) self._delete_vcmp_device_network(bigip, vlan_name) def _delete_device_vxlan(self, bigip, network, network_folder): # Delete vxlan tunnel on specific bigip tunnel_name = _get_tunnel_name(network) self.network_helper.delete_all_fdb_entries( bigip, tunnel_name, partition=network_folder) self.network_helper.delete_tunnel( bigip, tunnel_name, partition=network_folder) if self.fdb_connector: self.fdb_connector.notify_vtep_removed(network, bigip.local_ip) def _delete_device_gre(self, bigip, network, network_folder): # Delete gre tunnel on specific bigip tunnel_name = _get_tunnel_name(network) self.network_helper.delete_all_fdb_entries( bigip, tunnel_name, partition=network_folder) self.network_helper.delete_tunnel( bigip, tunnel_name, partition=network_folder) if self.fdb_connector: self.fdb_connector.notify_vtep_removed(network, bigip.local_ip) def _delete_vcmp_device_network(self, bigip, vlan_name): # For vCMP Guests, disassociate VLAN from vCMP Guest and # delete VLAN from vCMP Host. if not self.vcmp_manager: return vcmp_host = self.vcmp_manager.get_vcmp_host(bigip) if not vcmp_host: return # Remove VLAN association from the vCMP Guest vcmp_guest = self.vcmp_manager.get_vcmp_guest(vcmp_host, bigip) try: # REVISIT: the extra attributes in vcmp bigips need to be # worked on. vlan_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\ create('Common.StringSequence') vlan_seq.values = prefixed(vlan_name) vlan_seq_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\ create('Common.StringSequenceSequence') vlan_seq_seq.values = [vlan_seq] vcmp_host['bigip'].system.sys_vcmp.remove_vlan( [vcmp_guest['name']], vlan_seq_seq) LOG.debug(('Removed VLAN %s association from vCMP Guest %s' % (vlan_name, vcmp_guest['mgmt_addr']))) except Exception as exc: LOG.error(('Exception removing VLAN %s association from vCMP ' 'Guest %s:%s' % (vlan_name, vcmp_guest['mgmt_addr'], exc))) # Only delete VLAN if it is not in use by other vCMP Guests if self.vcmp_manager.get_vlan_use_count(vcmp_host, vlan_name): LOG.debug(('VLAN %s in use by other vCMP Guests on vCMP Host %s' % (vlan_name, vcmp_host['bigip'].hostname))) return # Delete VLAN from vCMP Host. This will fail if any other vCMP Guest # is using this VLAN try: self.network_helper.delete_vlan( vcmp_host['bigip'], vlan_name) LOG.debug(('Deleted VLAN %s from vCMP Host %s' % (vlan_name, vcmp_host['bigip'].hostname))) except Exception as exc: LOG.error(('Exception deleting VLAN %s from vCMP Host %s:%s' % (vlan_name, vcmp_host['bigip'].icontrol.hostname, exc))) def add_bigip_fdbs(self, bigip, net_folder, fdb_info, vteps_by_type): # Add fdb records for a mac/ip with specified vteps network = fdb_info['network'] net_type = network['provider:network_type'] vteps_key = net_type + '_vteps' if vteps_key in vteps_by_type: vteps = vteps_by_type[vteps_key] if net_type == 'gre': self.add_gre_fdbs(bigip, net_folder, fdb_info, vteps) elif net_type == 'vxlan': self.add_vxlan_fdbs(bigip, net_folder, fdb_info, vteps) def add_gre_fdbs(self, bigip, net_folder, fdb_info, vteps): # Add gre fdb records network = fdb_info['network'] ip_address = fdb_info['ip_address'] mac_address = fdb_info['mac_address'] tunnel_name = _get_tunnel_name(network) for vtep in vteps: if mac_address: mac_addr = mac_address else: mac_addr = _get_tunnel_fake_mac(network, vtep) # REVISIT(Rich Browne) caller must provide folder and not tenant_id self.network_helper.add_fdb_entry( bigip, tunnel_name=tunnel_name, partition=net_folder, mac_address=mac_addr, vtep_ip_address=vtep, arp_ip_address=ip_address) def add_vxlan_fdbs(self, bigip, net_folder, fdb_info, vteps): # Add vxlan fdb records network = fdb_info['network'] ip_address = fdb_info['ip_address'] mac_address = fdb_info['mac_address'] tunnel_name = _get_tunnel_name(network) for vtep in vteps: if mac_address: mac_addr = mac_address else: mac_addr = _get_tunnel_fake_mac(network, vtep) # REVISIT caller must provide folder and not tenant_id self.network_helper.add_fdb_entry( bigip, tunnel_name=tunnel_name, partition=net_folder, mac_address=mac_addr, vtep_ip_address=vtep, arp_ip_address=ip_address) def delete_bigip_fdbs(self, bigip, net_folder, fdb_info, vteps_by_type): # Delete fdb records for a mac/ip with specified vteps network = fdb_info['network'] net_type = network['provider:network_type'] vteps_key = net_type + '_vteps' if vteps_key in vteps_by_type: vteps = vteps_by_type[vteps_key] if net_type == 'gre': self.delete_gre_fdbs(bigip, net_folder, fdb_info, vteps) elif net_type == 'vxlan': self.delete_vxlan_fdbs(bigip, net_folder, fdb_info, vteps) def delete_gre_fdbs(self, bigip, net_folder, fdb_info, vteps): # delete gre fdb records network = fdb_info['network'] ip_address = fdb_info['ip_address'] mac_address = fdb_info['mac_address'] tunnel_name = _get_tunnel_name(network) for vtep in vteps: if mac_address: mac_addr = mac_address else: mac_addr = _get_tunnel_fake_mac(network, vtep) self.network_helper.delete_fdb_entry( bigip, tunnel_name=tunnel_name, mac_address=mac_addr, arp_ip_address=ip_address, partition=net_folder) def delete_vxlan_fdbs(self, bigip, net_folder, fdb_info, vteps): # delete vxlan fdb records network = fdb_info['network'] ip_address = fdb_info['ip_address'] mac_address = fdb_info['mac_address'] tunnel_name = _get_tunnel_name(network) for vtep in vteps: if mac_address: mac_addr = mac_address else: mac_addr = _get_tunnel_fake_mac(network, vtep) self.network_helper.delete_fdb_entry( bigip, tunnel_name=tunnel_name, mac_address=mac_addr, arp_ip_address=ip_address, partition=net_folder) def add_bigip_fdb(self, bigip, fdb): # Add entries from the fdb relevant to the bigip for fdb_operation in \ [{'network_type': 'vxlan', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.add_fdb_entries}, {'network_type': 'gre', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.add_fdb_entries}]: self._operate_bigip_fdb(bigip, fdb, fdb_operation) def _operate_bigip_fdb(self, bigip, fdb, fdb_operation): """Add L2 records for MAC addresses behind tunnel endpoints. Description of fdb structure: {'<network_id>': 'segment_id': <int> 'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ] '<network_id>': 'segment_id': 'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ] } Sample real fdb structure: {u'45bbbce1-191b-4f7b-84c5-54c6c8243bd2': {u'segment_id': 1008, u'ports': {u'10.30.30.2': [[u'00:00:00:00:00:00', u'0.0.0.0'], [u'fa:16:3e:3d:7b:7f', u'10.10.1.4']]}, u'network_type': u'vxlan'}} """ network_type = fdb_operation['network_type'] get_tunnel_folder = fdb_operation['get_tunnel_folder'] fdb_method = fdb_operation['fdb_method'] for network in fdb: net_fdb = fdb[network] if net_fdb['network_type'] == network_type: net = {'name': network, 'provider:network_type': net_fdb['network_type'], 'provider:segmentation_id': net_fdb['segment_id']} tunnel_name = _get_tunnel_name(net) folder = get_tunnel_folder(bigip, tunnel_name=tunnel_name) net_info = {'network': network, 'folder': folder, 'tunnel_name': tunnel_name, 'net_fdb': net_fdb} fdbs = self._get_bigip_network_fdbs(bigip, net_info) if len(fdbs) > 0: fdb_method(fdb_entries=fdbs) def _get_bigip_network_fdbs(self, bigip, net_info): # Get network fdb entries to add to a bigip if not net_info['folder']: return {} net_fdb = net_info['net_fdb'] fdbs = {} for vtep in net_fdb['ports']: # bigip does not need to set fdb entries for local addresses if vtep == bigip.local_ip: continue # most net_info applies to the vtep vtep_info = dict(net_info) # but the network fdb is too broad so delete it del vtep_info['net_fdb'] # use a slice of the fdb for the vtep instead vtep_info['vtep'] = vtep vtep_info['fdb_entries'] = net_fdb['ports'][vtep] self._merge_vtep_fdbs(vtep_info, fdbs) return fdbs def _merge_vtep_fdbs(self, vtep_info, fdbs): # Add L2 records for a specific network+vtep folder = vtep_info['folder'] tunnel_name = vtep_info['tunnel_name'] for entry in vtep_info['fdb_entries']: mac_address = entry[0] if mac_address == '00:00:00:00:00:00': continue ip_address = entry[1] # create/get tunnel data if tunnel_name not in fdbs: fdbs[tunnel_name] = {} tunnel_fdbs = fdbs[tunnel_name] # update tunnel folder tunnel_fdbs['folder'] = folder # maybe create records for tunnel if 'records' not in tunnel_fdbs: tunnel_fdbs['records'] = {} # add entry to records map keyed by mac address tunnel_fdbs['records'][mac_address] = \ {'endpoint': vtep_info['vtep'], 'ip_address': ip_address} def update_bigip_fdb(self, bigip, fdb): # Update l2 records self.add_bigip_fdb(bigip, fdb) def remove_bigip_fdb(self, bigip, fdb): # Add L2 records for MAC addresses behind tunnel endpoints for fdb_operation in \ [{'network_type': 'vxlan', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.delete_fdb_entries}, {'network_type': 'gre', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.delete_fdb_entries}]: self._operate_bigip_fdb(bigip, fdb, fdb_operation) # Utilities def get_network_name(self, bigip, network): # This constructs a name for a tunnel or vlan interface preserve_network_name = False if network['id'] in self.conf.common_network_ids: network_name = self.conf.common_network_ids[network['id']] preserve_network_name = True elif network['provider:network_type'] == 'vlan': network_name = self.get_vlan_name(network, bigip.hostname) elif network['provider:network_type'] == 'flat': network_name = self.get_vlan_name(network, bigip.hostname) elif network['provider:network_type'] == 'vxlan': network_name = _get_tunnel_name(network) elif network['provider:network_type'] == 'gre': network_name = _get_tunnel_name(network) else: error_message = 'Unsupported network type %s.' \ % network['provider:network_type'] + \ ' Cannot setup selfip or snat.' LOG.error(error_message) raise f5ex.InvalidNetworkType(error_message) return network_name, preserve_network_name
class L2ServiceBuilder(object): def __init__(self, driver, f5_global_routed_mode): self.conf = driver.conf self.driver = driver self.f5_global_routed_mode = f5_global_routed_mode self.vlan_binding = None self.fdb_connector = None self.interface_mapping = {} self.tagging_mapping = {} self.system_helper = SystemHelper() self.network_helper = NetworkHelper() self.service_adapter = ServiceModelAdapter(self.conf) if not f5_global_routed_mode: self.fdb_connector = FDBConnectorML2(self.conf) if self.conf.vlan_binding_driver: try: self.vlan_binding = importutils.import_object( self.conf.vlan_binding_driver, self.conf, self) except ImportError: LOG.error('Failed to import VLAN binding driver: %s' % self.conf.vlan_binding_driver) raise # map format is phynet:interface:tagged for maps in self.conf.f5_external_physical_mappings: intmap = maps.split(':') net_key = str(intmap[0]).strip() if len(intmap) > 3: net_key = net_key + ':' + str(intmap[3]).strip() self.interface_mapping[net_key] = str(intmap[1]).strip() self.tagging_mapping[net_key] = str(intmap[2]).strip() LOG.debug('physical_network %s = interface %s, tagged %s' % (net_key, intmap[1], intmap[2])) def initialize_vcmp_manager(self): '''Intialize the vCMP manager when the driver is ready.''' self.vcmp_manager = VcmpManager(self.driver) def post_init(self): if self.vlan_binding: LOG.debug( 'Getting BIG-IP device interface for VLAN Binding') self.vlan_binding.register_bigip_interfaces() def tunnel_sync(self, tunnel_ips): if self.fdb_connector: self.fdb_connector.advertise_tunnel_ips(tunnel_ips) def set_tunnel_rpc(self, tunnel_rpc): # Provide FDB Connector with ML2 RPC access if self.fdb_connector: self.fdb_connector.set_tunnel_rpc(tunnel_rpc) def set_l2pop_rpc(self, l2pop_rpc): # Provide FDB Connector with ML2 RPC access if self.fdb_connector: self.fdb_connector.set_l2pop_rpc(l2pop_rpc) def set_context(self, context): if self.fdb_connector: self.fdb_connector.set_context(context) def is_common_network(self, network): """Returns True if this belongs in the /Common folder This object method will return positive if the L2ServiceBuilder object should be stored under the Common partition on the BIG-IP. """ return network['shared'] or \ self.conf.f5_common_networks or \ (network['id'] in self.conf.common_network_ids) or \ ('router:external' in network and network['router:external'] and self.conf.f5_common_external_networks) def get_vlan_name(self, network, hostname): # Construct a consistent vlan name net_key = network['provider:physical_network'] net_type = network['provider:network_type'] # look for host specific interface mapping if net_key and net_key + ':' + hostname in self.interface_mapping: interface = self.interface_mapping[net_key + ':' + hostname] tagged = self.tagging_mapping[net_key + ':' + hostname] elif net_key and net_key in self.interface_mapping: interface = self.interface_mapping[net_key] tagged = self.tagging_mapping[net_key] else: interface = self.interface_mapping['default'] tagged = self.tagging_mapping['default'] if tagged: vlanid = network['provider:segmentation_id'] else: vlanid = 0 if net_type == "flat": interface_name = str(interface).replace(".", "-") if (len(interface_name) > 15): LOG.warn( "Interface name is greater than 15 chars in length") vlan_name = "flat-%s" % (interface_name) else: vlan_name = "vlan-%d" % (vlanid) return vlan_name def assure_bigip_network(self, bigip, network): # Ensure bigip has configured network object if not network: LOG.error('assure_bigip_network: ' 'Attempted to assure a network with no id..skipping.') return if network['id'] in bigip.assured_networks: return if network['id'] in self.conf.common_network_ids: LOG.debug('assure_bigip_network: ' 'Network is a common global network... skipping.') return LOG.debug("assure_bigip_network network: %s" % str(network)) start_time = time() if self.is_common_network(network): network_folder = 'Common' else: network_folder = self.service_adapter.get_folder_name( network['tenant_id'] ) # setup all needed L2 network segments if network['provider:network_type'] == 'flat': network_name = self._assure_device_network_flat( network, bigip, network_folder) elif network['provider:network_type'] == 'vlan': network_name = self._assure_device_network_vlan( network, bigip, network_folder) elif network['provider:network_type'] == 'vxlan': network_name = self._assure_device_network_vxlan( network, bigip, network_folder) elif network['provider:network_type'] == 'gre': network_name = self._assure_device_network_gre( network, bigip, network_folder) elif network['provider:network_type'] == 'opflex': raise f5_ex.NetworkNotReady( "Opflex network segment definition required") else: error_message = 'Unsupported network type %s.' \ % network['provider:network_type'] + \ ' Cannot setup network.' LOG.error(error_message) raise f5_ex.InvalidNetworkType(error_message) bigip.assured_networks[network['id']] = network_name if time() - start_time > .001: LOG.debug(" assure bigip network took %.5f secs" % (time() - start_time)) def _assure_device_network_flat(self, network, bigip, network_folder): # Ensure bigip has configured flat vlan (untagged) vlan_name = "" interface = self.interface_mapping['default'] vlanid = 0 # Do we have host specific mappings? net_key = network['provider:physical_network'] if net_key and net_key + ':' + bigip.hostname in \ self.interface_mapping: interface = self.interface_mapping[ net_key + ':' + bigip.hostname] # Do we have a mapping for this network elif net_key and net_key in self.interface_mapping: interface = self.interface_mapping[net_key] vlan_name = self.get_vlan_name(network, bigip.hostname) self._assure_vcmp_device_network(bigip, vlan={'name': vlan_name, 'folder': network_folder, 'id': vlanid, 'interface': interface, 'network': network}) if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip): interface = None try: model = {'name': vlan_name, 'interface': interface, 'partition': network_folder, 'description': network['id'], 'route_domain_id': network['route_domain_id']} self.network_helper.create_vlan(bigip, model) except Exception as err: LOG.exception("%s", err.message) raise f5_ex.VLANCreationException("Failed to create flat network") return vlan_name def _assure_device_network_vlan(self, network, bigip, network_folder): # Ensure bigip has configured tagged vlan # VLAN names are limited to 64 characters including # the folder name, so we name them foolish things. vlan_name = "" interface = self.interface_mapping['default'] tagged = self.tagging_mapping['default'] # Do we have host specific mappings? net_key = network['provider:physical_network'] if net_key and net_key + ':' + bigip.hostname in \ self.interface_mapping: interface = self.interface_mapping[ net_key + ':' + bigip.hostname] tagged = self.tagging_mapping[ net_key + ':' + bigip.hostname] # Do we have a mapping for this network elif net_key and net_key in self.interface_mapping: interface = self.interface_mapping[net_key] tagged = self.tagging_mapping[net_key] if tagged: vlanid = network['provider:segmentation_id'] else: vlanid = 0 vlan_name = self.get_vlan_name(network, bigip.hostname) self._assure_vcmp_device_network(bigip, vlan={'name': vlan_name, 'folder': network_folder, 'id': vlanid, 'interface': interface, 'network': network}) if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip): interface = None try: model = {'name': vlan_name, 'interface': interface, 'tag': vlanid, 'partition': network_folder, 'description': network['id'], 'route_domain_id': network['route_domain_id']} self.network_helper.create_vlan(bigip, model) except Exception as err: LOG.exception("%s", err.message) raise f5_ex.VLANCreationException( "Failed to create vlan: %s" % vlan_name ) if self.vlan_binding: self.vlan_binding.allow_vlan( device_name=bigip.device_name, interface=interface, vlanid=vlanid ) return vlan_name def _assure_device_network_vxlan(self, network, bigip, partition): # Ensure bigip has configured vxlan tunnel_name = "" if not bigip.local_ip: error_message = 'Cannot create tunnel %s on %s' \ % (network['id'], bigip.hostname) error_message += ' no VTEP SelfIP defined.' LOG.error('VXLAN:' + error_message) raise f5_ex.MissingVTEPAddress('VXLAN:' + error_message) tunnel_name = _get_tunnel_name(network) # create the main tunnel entry for the fdb records payload = {'name': tunnel_name, 'partition': partition, 'profile': 'vxlan_ovs', 'key': network['provider:segmentation_id'], 'localAddress': bigip.local_ip, 'description': network['id'], 'route_domain_id': network['route_domain_id']} try: self.network_helper.create_multipoint_tunnel(bigip, payload) except Exception as err: LOG.exception("%s", err.message) raise f5_ex.VXLANCreationException( "Failed to create vxlan tunnel: %s" % tunnel_name ) if self.fdb_connector: self.fdb_connector.notify_vtep_added(network, bigip.local_ip) return tunnel_name def _assure_device_network_gre(self, network, bigip, partition): tunnel_name = "" # Ensure bigip has configured gre tunnel if not bigip.local_ip: error_message = 'Cannot create tunnel %s on %s' \ % (network['id'], bigip.hostname) error_message += ' no VTEP SelfIP defined.' LOG.error('L2GRE:' + error_message) raise f5_ex.MissingVTEPAddress('L2GRE:' + error_message) tunnel_name = _get_tunnel_name(network) payload = {'name': tunnel_name, 'partition': partition, 'profile': 'gre_ovs', 'key': network['provider:segmentation_id'], 'localAddress': bigip.local_ip, 'description': network['id'], 'route_domain_id': network['route_domain_id']} try: self.network_helper.create_multipoint_tunnel(bigip, payload) except Exception as err: LOG.exception("%s", err.message) raise f5_ex.VXLANCreationException( "Failed to create gre tunnel: %s" % tunnel_name ) if self.fdb_connector: self.fdb_connector.notify_vtep_added(network, bigip.local_ip) return tunnel_name def _assure_vcmp_device_network(self, bigip, vlan): if not self.vcmp_manager: return vcmp_host = self.vcmp_manager.get_vcmp_host(bigip) if not vcmp_host: return # Create the VLAN on the vCMP Host model = {'name': vlan['name'], 'partition': 'Common', 'tag': vlan['id'], 'interface': vlan['interface'], 'description': vlan['network']['id'], 'route_domain_id': vlan['network']['route_domain_id']} try: self.network_helper.create_vlan(vcmp_host['bigip'], model) LOG.debug(('Created VLAN %s on vCMP Host %s' % (vlan['name'], vcmp_host['bigip'].hostname))) except Exception as exc: LOG.error( ('Exception creating VLAN %s on vCMP Host %s:%s' % (vlan['name'], vcmp_host['bigip'].hostname, exc))) # Associate the VLAN with the vCMP Guest, if necessary self.vcmp_manager.assoc_vlan_with_vcmp_guest(bigip, vlan) def delete_bigip_network(self, bigip, network): # Delete network on bigip if network['id'] in self.conf.common_network_ids: LOG.debug('skipping delete of common network %s' % network['id']) return if self.is_common_network(network): network_folder = 'Common' else: network_folder = self.service_adapter.get_folder_name( network['tenant_id']) if network['provider:network_type'] == 'vlan': self._delete_device_vlan(bigip, network, network_folder) elif network['provider:network_type'] == 'flat': self._delete_device_flat(bigip, network, network_folder) elif network['provider:network_type'] == 'vxlan': self._delete_device_vxlan(bigip, network, network_folder) elif network['provider:network_type'] == 'gre': self._delete_device_gre(bigip, network, network_folder) elif network['provider:network_type'] == 'opflex': raise f5_ex.NetworkNotReady( "Opflex network segment definition required") else: LOG.error('Unsupported network type %s. Can not delete.' % network['provider:network_type']) if network['id'] in bigip.assured_networks: del bigip.assured_networks[network['id']] def _delete_device_vlan(self, bigip, network, network_folder): # Delete tagged vlan on specific bigip vlan_name = self.get_vlan_name(network, bigip.hostname) try: self.network_helper.delete_vlan( bigip, vlan_name, partition=network_folder ) except Exception as err: LOG.exception(err) LOG.error( "Failed to delete vlan: %s" % vlan_name) if self.vlan_binding: interface = self.interface_mapping['default'] tagged = self.tagging_mapping['default'] vlanid = 0 # Do we have host specific mappings? net_key = network['provider:physical_network'] if net_key and net_key + ':' + bigip.hostname in \ self.interface_mapping: interface = self.interface_mapping[ net_key + ':' + bigip.hostname] tagged = self.tagging_mapping[ net_key + ':' + bigip.hostname] # Do we have a mapping for this network elif net_key and net_key in self.interface_mapping: interface = self.interface_mapping[net_key] tagged = self.tagging_mapping[net_key] if tagged: vlanid = network['provider:segmentation_id'] else: vlanid = 0 self.vlan_binding.prune_vlan( device_name=bigip.device_name, interface=interface, vlanid=vlanid ) self._delete_vcmp_device_network(bigip, vlan_name) def _delete_device_flat(self, bigip, network, network_folder): # Delete untagged vlan on specific bigip vlan_name = self.get_vlan_name(network, bigip.hostname) try: self.network_helper.delete_vlan( bigip, vlan_name, partition=network_folder ) except Exception as err: LOG.exception(err) LOG.error( "Failed to delete vlan: %s" % vlan_name) self._delete_vcmp_device_network(bigip, vlan_name) def _delete_device_vxlan(self, bigip, network, network_folder): # Delete vxlan tunnel on specific bigip tunnel_name = _get_tunnel_name(network) try: self.network_helper.delete_all_fdb_entries( bigip, tunnel_name, partition=network_folder) self.network_helper.delete_tunnel( bigip, tunnel_name, partition=network_folder) except Exception as err: # Just log the exception, we want to continue cleanup LOG.exception(err) LOG.error( "Failed to delete vxlan tunnel: %s" % tunnel_name) if self.fdb_connector: self.fdb_connector.notify_vtep_removed(network, bigip.local_ip) def _delete_device_gre(self, bigip, network, network_folder): # Delete gre tunnel on specific bigip tunnel_name = _get_tunnel_name(network) try: self.network_helper.delete_all_fdb_entries( bigip, tunnel_name, partition=network_folder) self.network_helper.delete_tunnel( bigip, tunnel_name, partition=network_folder) except Exception as err: # Just log the exception, we want to continue cleanup LOG.exception(err) LOG.error( "Failed to delete gre tunnel: %s" % tunnel_name) if self.fdb_connector: self.fdb_connector.notify_vtep_removed(network, bigip.local_ip) def _delete_vcmp_device_network(self, bigip, vlan_name): '''Disassociated VLAN with vCMP Guest, then delete it from vCMP Host :param bigip: ManagementRoot object -- vCMP guest :param vlan_name: str -- name of vlan ''' if not self.vcmp_manager: return vcmp_host = self.vcmp_manager.get_vcmp_host(bigip) if not vcmp_host: return self.vcmp_manager.disassoc_vlan_with_vcmp_guest(bigip, vlan_name) def add_bigip_fdb(self, bigip, fdb): # Add entries from the fdb relevant to the bigip for fdb_operation in \ [{'network_type': 'vxlan', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.add_fdb_entries}, {'network_type': 'gre', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.add_fdb_entries}]: self._operate_bigip_fdb(bigip, fdb, fdb_operation) def _operate_bigip_fdb(self, bigip, fdb, fdb_operation): """Add L2 records for MAC addresses behind tunnel endpoints. Description of fdb structure: {'<network_id>': 'segment_id': <int> 'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ] '<network_id>': 'segment_id': 'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ] } Sample real fdb structure: {u'45bbbce1-191b-4f7b-84c5-54c6c8243bd2': {u'segment_id': 1008, u'ports': {u'10.30.30.2': [[u'00:00:00:00:00:00', u'0.0.0.0'], [u'fa:16:3e:3d:7b:7f', u'10.10.1.4']]}, u'network_type': u'vxlan'}} """ network_type = fdb_operation['network_type'] get_tunnel_folder = fdb_operation['get_tunnel_folder'] fdb_method = fdb_operation['fdb_method'] for network in fdb: net_fdb = fdb[network] if net_fdb['network_type'] == network_type: net = {'name': network, 'provider:network_type': net_fdb['network_type'], 'provider:segmentation_id': net_fdb['segment_id']} tunnel_name = _get_tunnel_name(net) folder = get_tunnel_folder(bigip, tunnel_name=tunnel_name) net_info = {'network': network, 'folder': folder, 'tunnel_name': tunnel_name, 'net_fdb': net_fdb} fdbs = self._get_bigip_network_fdbs(bigip, net_info) if len(fdbs) > 0: fdb_method(bigip, fdb_entries=fdbs) def _get_bigip_network_fdbs(self, bigip, net_info): # Get network fdb entries to add to a bigip if not net_info['folder']: return {} net_fdb = net_info['net_fdb'] fdbs = {} for vtep in net_fdb['ports']: # bigip does not need to set fdb entries for local addresses if vtep == bigip.local_ip: continue # most net_info applies to the vtep vtep_info = dict(net_info) # but the network fdb is too broad so delete it del vtep_info['net_fdb'] # use a slice of the fdb for the vtep instead vtep_info['vtep'] = vtep vtep_info['fdb_entries'] = net_fdb['ports'][vtep] self._merge_vtep_fdbs(vtep_info, fdbs) return fdbs def _merge_vtep_fdbs(self, vtep_info, fdbs): # Add L2 records for a specific network+vtep folder = vtep_info['folder'] tunnel_name = vtep_info['tunnel_name'] for entry in vtep_info['fdb_entries']: mac_address = entry[0] if mac_address == '00:00:00:00:00:00': continue ip_address = entry[1] # create/get tunnel data if tunnel_name not in fdbs: fdbs[tunnel_name] = {} tunnel_fdbs = fdbs[tunnel_name] # update tunnel folder tunnel_fdbs['folder'] = folder # maybe create records for tunnel if 'records' not in tunnel_fdbs: tunnel_fdbs['records'] = {} # add entry to records map keyed by mac address tunnel_fdbs['records'][mac_address] = \ {'endpoint': vtep_info['vtep'], 'ip_address': ip_address} def update_bigip_fdb(self, bigip, fdb): # Update l2 records self.add_bigip_fdb(bigip, fdb) def remove_bigip_fdb(self, bigip, fdb): # Add L2 records for MAC addresses behind tunnel endpoints for fdb_operation in \ [{'network_type': 'vxlan', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.delete_fdb_entries}, {'network_type': 'gre', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.delete_fdb_entries}]: self._operate_bigip_fdb(bigip, fdb, fdb_operation) # Utilities def get_network_name(self, bigip, network): # This constructs a name for a tunnel or vlan interface preserve_network_name = False if network['id'] in self.conf.common_network_ids: network_name = self.conf.common_network_ids[network['id']] preserve_network_name = True elif network['provider:network_type'] == 'vlan': network_name = self.get_vlan_name(network, bigip.hostname) elif network['provider:network_type'] == 'flat': network_name = self.get_vlan_name(network, bigip.hostname) elif network['provider:network_type'] == 'vxlan': network_name = _get_tunnel_name(network) elif network['provider:network_type'] == 'gre': network_name = _get_tunnel_name(network) else: error_message = 'Unsupported network type %s.' \ % network['provider:network_type'] + \ ' Cannot setup selfip or snat.' LOG.error(error_message) raise f5_ex.InvalidNetworkType(error_message) return network_name, preserve_network_name def _get_network_folder(self, network): if self.is_common_network(network): return 'Common' else: return self.service_adapter.get_folder_name(network['tenant_id']) def add_fdb_entries(self, bigips, loadbalancer, members): """Update fdb entries for loadbalancer and member VTEPs. :param bigips: one or more BIG-IPs to update. :param loadbalancer: Loadbalancer with VTEPs to update. Can be None. :param members: List of members. Can be emtpy ([]). """ tunnel_records = self.create_fdb_records(loadbalancer, members) if tunnel_records: for bigip in bigips: self.network_helper.add_fdb_entries(bigip, fdb_entries=tunnel_records) def delete_fdb_entries(self, bigips, loadbalancer, members): """Remove fdb entries for loadbalancer and member VTEPs. :param bigips: one or more BIG-IPs to update. :param loadbalancer: Loadbalancer with VTEPs to remove. Can be None. :param members: List of members. Can be emtpy ([]). """ tunnel_records = self.create_fdb_records(loadbalancer, members) if tunnel_records: for bigip in bigips: self.network_helper.delete_fdb_entries( bigip, fdb_entries=tunnel_records) def create_fdb_records(self, loadbalancer, members): fdbs = dict() if loadbalancer: network = loadbalancer['network'] tunnel_name = _get_tunnel_name(network) fdbs[tunnel_name] = dict() fdbs[tunnel_name]['folder'] = self._get_network_folder(network) records = dict() fdbs[tunnel_name]['records'] = records self.append_loadbalancer_fdb_records( network, loadbalancer, records) for member in members: network = member['network'] tunnel_name = _get_tunnel_name(network) if tunnel_name not in fdbs: fdbs[tunnel_name] = dict() fdbs[tunnel_name]['folder'] = self._get_network_folder(network) fdbs[tunnel_name]['records'] = dict() records = fdbs[tunnel_name]['records'] if 'port' in member and 'mac_address' in member['port']: mac_addr = member['port']['mac_address'] self.append_member_fdb_records(network, member, records, mac_addr, ip_address=member['address']) return fdbs def append_loadbalancer_fdb_records(self, network, loadbalancer, records): vteps = _get_vteps(network, loadbalancer) for vtep in vteps: # create an arbitrary MAC address for VTEP mac_addr = _get_tunnel_fake_mac(network, vtep) records[mac_addr] = {'endpoint': vtep, 'ip_address': ''} def append_member_fdb_records(self, network, member, records, mac_addr, ip_address=''): vteps = _get_vteps(network, member) for vtep in vteps: records[mac_addr] = {'endpoint': vtep, 'ip_address': ip_address}
class L2ServiceBuilder(object): def __init__(self, driver, f5_global_routed_mode): self.conf = driver.conf self.driver = driver self.f5_global_routed_mode = f5_global_routed_mode self.vlan_binding = None self.fdb_connector = None self.interface_mapping = {} self.tagging_mapping = {} self.system_helper = SystemHelper() self.network_helper = NetworkHelper() self.service_adapter = ServiceModelAdapter(self.conf) if not f5_global_routed_mode: self.fdb_connector = FDBConnectorML2(self.conf) if self.conf.vlan_binding_driver: try: self.vlan_binding = importutils.import_object( self.conf.vlan_binding_driver, self.conf, self) except ImportError: LOG.error('Failed to import VLAN binding driver: %s' % self.conf.vlan_binding_driver) raise # map format is phynet:interface:tagged for maps in self.conf.f5_external_physical_mappings: intmap = maps.split(':') net_key = str(intmap[0]).strip() if len(intmap) > 3: net_key = net_key + ':' + str(intmap[3]).strip() self.interface_mapping[net_key] = str(intmap[1]).strip() self.tagging_mapping[net_key] = str(intmap[2]).strip() LOG.debug('physical_network %s = interface %s, tagged %s' % (net_key, intmap[1], intmap[2])) def initialize_vcmp_manager(self): '''Intialize the vCMP manager when the driver is ready.''' self.vcmp_manager = VcmpManager(self.driver) def post_init(self): if self.vlan_binding: LOG.debug( 'Getting BIG-IP device interface for VLAN Binding') self.vlan_binding.register_bigip_interfaces() def tunnel_sync(self, tunnel_ips): if self.fdb_connector: self.fdb_connector.advertise_tunnel_ips(tunnel_ips) def set_tunnel_rpc(self, tunnel_rpc): # Provide FDB Connector with ML2 RPC access if self.fdb_connector: self.fdb_connector.set_tunnel_rpc(tunnel_rpc) def set_l2pop_rpc(self, l2pop_rpc): # Provide FDB Connector with ML2 RPC access if self.fdb_connector: self.fdb_connector.set_l2pop_rpc(l2pop_rpc) def set_context(self, context): if self.fdb_connector: self.fdb_connector.set_context(context) def is_common_network(self, network): # Does this network belong in the /Common folder? return network['shared'] or \ (network['id'] in self.conf.common_network_ids) or \ ('router:external' in network and network['router:external'] and self.conf.f5_common_external_networks) def get_vlan_name(self, network, hostname): # Construct a consistent vlan name net_key = network['provider:physical_network'] net_type = network['provider:network_type'] # look for host specific interface mapping if net_key + ':' + hostname in self.interface_mapping: interface = self.interface_mapping[net_key + ':' + hostname] tagged = self.tagging_mapping[net_key + ':' + hostname] elif net_key in self.interface_mapping: interface = self.interface_mapping[net_key] tagged = self.tagging_mapping[net_key] else: interface = self.interface_mapping['default'] tagged = self.tagging_mapping['default'] if tagged: vlanid = network['provider:segmentation_id'] else: vlanid = 0 if net_type == "flat": interface_name = str(interface).replace(".", "-") if (len(interface_name) > 15): LOG.warn( "Interface name is greater than 15 chars in length") vlan_name = "flat-%s" % (interface_name) else: vlan_name = "vlan-%d" % (vlanid) return vlan_name def assure_bigip_network(self, bigip, network): # Ensure bigip has configured network object if not network: LOG.error('assure_bigip_network: ' 'Attempted to assure a network with no id..skipping.') return if network['id'] in bigip.assured_networks: return if network['id'] in self.conf.common_network_ids: LOG.debug('assure_bigip_network: ' 'Network is a common global network... skipping.') return LOG.debug("assure_bigip_network network: %s" % str(network)) start_time = time() if self.is_common_network(network): network_folder = 'Common' else: network_folder = self.service_adapter.get_folder_name( network['tenant_id'] ) # setup all needed L2 network segments if network['provider:network_type'] == 'flat': network_name = self._assure_device_network_flat( network, bigip, network_folder) elif network['provider:network_type'] == 'vlan': network_name = self._assure_device_network_vlan( network, bigip, network_folder) elif network['provider:network_type'] == 'vxlan': network_name = self._assure_device_network_vxlan( network, bigip, network_folder) elif network['provider:network_type'] == 'gre': network_name = self._assure_device_network_gre( network, bigip, network_folder) elif network['provider:network_type'] == 'opflex': raise f5_ex.NetworkNotReady( "Opflex network segment definition required") else: error_message = 'Unsupported network type %s.' \ % network['provider:network_type'] + \ ' Cannot setup network.' LOG.error(error_message) raise f5_ex.InvalidNetworkType(error_message) bigip.assured_networks[network['id']] = network_name if time() - start_time > .001: LOG.debug(" assure bigip network took %.5f secs" % (time() - start_time)) def _assure_device_network_flat(self, network, bigip, network_folder): # Ensure bigip has configured flat vlan (untagged) vlan_name = "" interface = self.interface_mapping['default'] vlanid = 0 # Do we have host specific mappings? net_key = network['provider:physical_network'] if net_key + ':' + bigip.hostname in \ self.interface_mapping: interface = self.interface_mapping[ net_key + ':' + bigip.hostname] # Do we have a mapping for this network elif net_key in self.interface_mapping: interface = self.interface_mapping[net_key] vlan_name = self.get_vlan_name(network, bigip.hostname) self._assure_vcmp_device_network(bigip, vlan={'name': vlan_name, 'folder': network_folder, 'id': vlanid, 'interface': interface, 'network': network}) if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip): interface = None try: model = {'name': vlan_name, 'interface': interface, 'partition': network_folder, 'description': network['id'], 'route_domain_id': network['route_domain_id']} self.network_helper.create_vlan(bigip, model) except Exception as err: LOG.exception("%s", err.message) raise f5_ex.VLANCreationException("Failed to create flat network") return vlan_name def _assure_device_network_vlan(self, network, bigip, network_folder): # Ensure bigip has configured tagged vlan # VLAN names are limited to 64 characters including # the folder name, so we name them foolish things. vlan_name = "" interface = self.interface_mapping['default'] tagged = self.tagging_mapping['default'] # Do we have host specific mappings? net_key = network['provider:physical_network'] if net_key + ':' + bigip.hostname in \ self.interface_mapping: interface = self.interface_mapping[ net_key + ':' + bigip.hostname] tagged = self.tagging_mapping[ net_key + ':' + bigip.hostname] # Do we have a mapping for this network elif net_key in self.interface_mapping: interface = self.interface_mapping[net_key] tagged = self.tagging_mapping[net_key] if tagged: vlanid = network['provider:segmentation_id'] else: vlanid = 0 vlan_name = self.get_vlan_name(network, bigip.hostname) self._assure_vcmp_device_network(bigip, vlan={'name': vlan_name, 'folder': network_folder, 'id': vlanid, 'interface': interface, 'network': network}) if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip): interface = None try: model = {'name': vlan_name, 'interface': interface, 'tag': vlanid, 'partition': network_folder, 'description': network['id'], 'route_domain_id': network['route_domain_id']} self.network_helper.create_vlan(bigip, model) except Exception as err: LOG.exception("%s", err.message) raise f5_ex.VLANCreationException( "Failed to create vlan: %s" % vlan_name ) if self.vlan_binding: self.vlan_binding.allow_vlan( device_name=bigip.device_name, interface=interface, vlanid=vlanid ) return vlan_name def _assure_device_network_vxlan(self, network, bigip, partition): # Ensure bigip has configured vxlan tunnel_name = "" if not bigip.local_ip: error_message = 'Cannot create tunnel %s on %s' \ % (network['id'], bigip.hostname) error_message += ' no VTEP SelfIP defined.' LOG.error('VXLAN:' + error_message) raise f5_ex.MissingVTEPAddress('VXLAN:' + error_message) tunnel_name = _get_tunnel_name(network) # create the main tunnel entry for the fdb records payload = {'name': tunnel_name, 'partition': partition, 'profile': 'vxlan_ovs', 'key': network['provider:segmentation_id'], 'localAddress': bigip.local_ip, 'description': network['id'], 'route_domain_id': network['route_domain_id']} try: self.network_helper.create_multipoint_tunnel(bigip, payload) except Exception as err: LOG.exception("%s", err.message) raise f5_ex.VXLANCreationException( "Failed to create vxlan tunnel: %s" % tunnel_name ) if self.fdb_connector: self.fdb_connector.notify_vtep_added(network, bigip.local_ip) return tunnel_name def _assure_device_network_gre(self, network, bigip, partition): tunnel_name = "" # Ensure bigip has configured gre tunnel if not bigip.local_ip: error_message = 'Cannot create tunnel %s on %s' \ % (network['id'], bigip.hostname) error_message += ' no VTEP SelfIP defined.' LOG.error('L2GRE:' + error_message) raise f5_ex.MissingVTEPAddress('L2GRE:' + error_message) tunnel_name = _get_tunnel_name(network) payload = {'name': tunnel_name, 'partition': partition, 'profile': 'gre_ovs', 'key': network['provider:segmentation_id'], 'localAddress': bigip.local_ip, 'description': network['id'], 'route_domain_id': network['route_domain_id']} try: self.network_helper.create_multipoint_tunnel(bigip, payload) except Exception as err: LOG.exception("%s", err.message) raise f5_ex.VXLANCreationException( "Failed to create gre tunnel: %s" % tunnel_name ) if self.fdb_connector: self.fdb_connector.notify_vtep_added(network, bigip.local_ip) return tunnel_name def _assure_vcmp_device_network(self, bigip, vlan): if not self.vcmp_manager: return vcmp_host = self.vcmp_manager.get_vcmp_host(bigip) if not vcmp_host: return # Create the VLAN on the vCMP Host model = {'name': vlan['name'], 'partition': 'Common', 'tag': vlan['id'], 'interface': vlan['interface'], 'description': vlan['network']['id'], 'route_domain_id': vlan['network']['route_domain_id']} try: self.network_helper.create_vlan(vcmp_host['bigip'], model) LOG.debug(('Created VLAN %s on vCMP Host %s' % (vlan['name'], vcmp_host['bigip'].hostname))) except Exception as exc: LOG.error( ('Exception creating VLAN %s on vCMP Host %s:%s' % (vlan['name'], vcmp_host['bigip'].hostname, exc))) # Associate the VLAN with the vCMP Guest, if necessary self.vcmp_manager.assoc_vlan_with_vcmp_guest(bigip, vlan) def delete_bigip_network(self, bigip, network): # Delete network on bigip if network['id'] in self.conf.common_network_ids: LOG.debug('skipping delete of common network %s' % network['id']) return if self.is_common_network(network): network_folder = 'Common' else: network_folder = self.service_adapter.get_folder_name( network['tenant_id']) if network['provider:network_type'] == 'vlan': self._delete_device_vlan(bigip, network, network_folder) elif network['provider:network_type'] == 'flat': self._delete_device_flat(bigip, network, network_folder) elif network['provider:network_type'] == 'vxlan': self._delete_device_vxlan(bigip, network, network_folder) elif network['provider:network_type'] == 'gre': self._delete_device_gre(bigip, network, network_folder) elif network['provider:network_type'] == 'opflex': raise f5_ex.NetworkNotReady( "Opflex network segment definition required") else: LOG.error('Unsupported network type %s. Can not delete.' % network['provider:network_type']) if network['id'] in bigip.assured_networks: del bigip.assured_networks[network['id']] def _delete_device_vlan(self, bigip, network, network_folder): # Delete tagged vlan on specific bigip vlan_name = self.get_vlan_name(network, bigip.hostname) try: self.network_helper.delete_vlan( bigip, vlan_name, partition=network_folder ) except Exception as err: LOG.exception(err) LOG.error( "Failed to delete vlan: %s" % vlan_name) if self.vlan_binding: interface = self.interface_mapping['default'] tagged = self.tagging_mapping['default'] vlanid = 0 # Do we have host specific mappings? net_key = network['provider:physical_network'] if net_key + ':' + bigip.hostname in \ self.interface_mapping: interface = self.interface_mapping[ net_key + ':' + bigip.hostname] tagged = self.tagging_mapping[ net_key + ':' + bigip.hostname] # Do we have a mapping for this network elif net_key in self.interface_mapping: interface = self.interface_mapping[net_key] tagged = self.tagging_mapping[net_key] if tagged: vlanid = network['provider:segmentation_id'] else: vlanid = 0 self.vlan_binding.prune_vlan( device_name=bigip.device_name, interface=interface, vlanid=vlanid ) self._delete_vcmp_device_network(bigip, vlan_name) def _delete_device_flat(self, bigip, network, network_folder): # Delete untagged vlan on specific bigip vlan_name = self.get_vlan_name(network, bigip.hostname) try: self.network_helper.delete_vlan( bigip, vlan_name, partition=network_folder ) except Exception as err: LOG.exception(err) LOG.error( "Failed to delete vlan: %s" % vlan_name) self._delete_vcmp_device_network(bigip, vlan_name) def _delete_device_vxlan(self, bigip, network, network_folder): # Delete vxlan tunnel on specific bigip tunnel_name = _get_tunnel_name(network) try: self.network_helper.delete_all_fdb_entries( bigip, tunnel_name, partition=network_folder) self.network_helper.delete_tunnel( bigip, tunnel_name, partition=network_folder) except Exception as err: # Just log the exception, we want to continue cleanup LOG.exception(err) LOG.error( "Failed to delete vxlan tunnel: %s" % tunnel_name) if self.fdb_connector: self.fdb_connector.notify_vtep_removed(network, bigip.local_ip) def _delete_device_gre(self, bigip, network, network_folder): # Delete gre tunnel on specific bigip tunnel_name = _get_tunnel_name(network) try: self.network_helper.delete_all_fdb_entries( bigip, tunnel_name, partition=network_folder) self.network_helper.delete_tunnel( bigip, tunnel_name, partition=network_folder) except Exception as err: # Just log the exception, we want to continue cleanup LOG.exception(err) LOG.error( "Failed to delete gre tunnel: %s" % tunnel_name) if self.fdb_connector: self.fdb_connector.notify_vtep_removed(network, bigip.local_ip) def _delete_vcmp_device_network(self, bigip, vlan_name): '''Disassociated VLAN with vCMP Guest, then delete it from vCMP Host :param bigip: ManagementRoot object -- vCMP guest :param vlan_name: str -- name of vlan ''' if not self.vcmp_manager: return vcmp_host = self.vcmp_manager.get_vcmp_host(bigip) if not vcmp_host: return self.vcmp_manager.disassoc_vlan_with_vcmp_guest(bigip, vlan_name) def add_bigip_fdbs(self, bigip, net_folder, fdb_info, vteps_by_type): # Add fdb records for a mac/ip with specified vteps network = fdb_info['network'] net_type = network['provider:network_type'] vteps_key = net_type + '_vteps' if vteps_key in vteps_by_type: vteps = vteps_by_type[vteps_key] if net_type == 'gre': self.add_gre_fdbs(bigip, net_folder, fdb_info, vteps) elif net_type == 'vxlan': self.add_vxlan_fdbs(bigip, net_folder, fdb_info, vteps) def add_gre_fdbs(self, bigip, net_folder, fdb_info, vteps): # Add gre fdb records network = fdb_info['network'] ip_address = fdb_info['ip_address'] mac_address = fdb_info['mac_address'] tunnel_name = _get_tunnel_name(network) for vtep in vteps: if mac_address: mac_addr = mac_address else: mac_addr = _get_tunnel_fake_mac(network, vtep) self.network_helper.add_fdb_entry( bigip, tunnel_name=tunnel_name, partition=net_folder, mac_address=mac_addr, vtep_ip_address=vtep, arp_ip_address=ip_address) def add_vxlan_fdbs(self, bigip, net_folder, fdb_info, vteps): # Add vxlan fdb records network = fdb_info['network'] ip_address = fdb_info['ip_address'] mac_address = fdb_info['mac_address'] tunnel_name = _get_tunnel_name(network) for vtep in vteps: if mac_address: mac_addr = mac_address else: mac_addr = _get_tunnel_fake_mac(network, vtep) self.network_helper.add_fdb_entry( bigip, tunnel_name=tunnel_name, partition=net_folder, mac_address=mac_addr, vtep_ip_address=vtep, arp_ip_address=ip_address) def delete_bigip_fdbs(self, bigip, net_folder, fdb_info, vteps_by_type): # Delete fdb records for a mac/ip with specified vteps network = fdb_info['network'] net_type = network['provider:network_type'] vteps_key = net_type + '_vteps' if vteps_key in vteps_by_type: vteps = vteps_by_type[vteps_key] if net_type == 'gre': self.delete_gre_fdbs(bigip, net_folder, fdb_info, vteps) elif net_type == 'vxlan': self.delete_vxlan_fdbs(bigip, net_folder, fdb_info, vteps) def delete_gre_fdbs(self, bigip, net_folder, fdb_info, vteps): # delete gre fdb records network = fdb_info['network'] ip_address = fdb_info['ip_address'] mac_address = fdb_info['mac_address'] tunnel_name = _get_tunnel_name(network) for vtep in vteps: if mac_address: mac_addr = mac_address else: mac_addr = _get_tunnel_fake_mac(network, vtep) self.network_helper.delete_fdb_entry( bigip, tunnel_name=tunnel_name, mac_address=mac_addr, arp_ip_address=ip_address, partition=net_folder) def delete_vxlan_fdbs(self, bigip, net_folder, fdb_info, vteps): # delete vxlan fdb records network = fdb_info['network'] ip_address = fdb_info['ip_address'] mac_address = fdb_info['mac_address'] tunnel_name = _get_tunnel_name(network) for vtep in vteps: if mac_address: mac_addr = mac_address else: mac_addr = _get_tunnel_fake_mac(network, vtep) self.network_helper.delete_fdb_entry( bigip, tunnel_name=tunnel_name, mac_address=mac_addr, arp_ip_address=ip_address, partition=net_folder) def add_bigip_fdb(self, bigip, fdb): # Add entries from the fdb relevant to the bigip for fdb_operation in \ [{'network_type': 'vxlan', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.add_fdb_entries}, {'network_type': 'gre', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.add_fdb_entries}]: self._operate_bigip_fdb(bigip, fdb, fdb_operation) def _operate_bigip_fdb(self, bigip, fdb, fdb_operation): """Add L2 records for MAC addresses behind tunnel endpoints. Description of fdb structure: {'<network_id>': 'segment_id': <int> 'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ] '<network_id>': 'segment_id': 'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ] } Sample real fdb structure: {u'45bbbce1-191b-4f7b-84c5-54c6c8243bd2': {u'segment_id': 1008, u'ports': {u'10.30.30.2': [[u'00:00:00:00:00:00', u'0.0.0.0'], [u'fa:16:3e:3d:7b:7f', u'10.10.1.4']]}, u'network_type': u'vxlan'}} """ network_type = fdb_operation['network_type'] get_tunnel_folder = fdb_operation['get_tunnel_folder'] fdb_method = fdb_operation['fdb_method'] for network in fdb: net_fdb = fdb[network] if net_fdb['network_type'] == network_type: net = {'name': network, 'provider:network_type': net_fdb['network_type'], 'provider:segmentation_id': net_fdb['segment_id']} tunnel_name = _get_tunnel_name(net) folder = get_tunnel_folder(bigip, tunnel_name=tunnel_name) net_info = {'network': network, 'folder': folder, 'tunnel_name': tunnel_name, 'net_fdb': net_fdb} fdbs = self._get_bigip_network_fdbs(bigip, net_info) if len(fdbs) > 0: fdb_method(fdb_entries=fdbs) def _get_bigip_network_fdbs(self, bigip, net_info): # Get network fdb entries to add to a bigip if not net_info['folder']: return {} net_fdb = net_info['net_fdb'] fdbs = {} for vtep in net_fdb['ports']: # bigip does not need to set fdb entries for local addresses if vtep == bigip.local_ip: continue # most net_info applies to the vtep vtep_info = dict(net_info) # but the network fdb is too broad so delete it del vtep_info['net_fdb'] # use a slice of the fdb for the vtep instead vtep_info['vtep'] = vtep vtep_info['fdb_entries'] = net_fdb['ports'][vtep] self._merge_vtep_fdbs(vtep_info, fdbs) return fdbs def _merge_vtep_fdbs(self, vtep_info, fdbs): # Add L2 records for a specific network+vtep folder = vtep_info['folder'] tunnel_name = vtep_info['tunnel_name'] for entry in vtep_info['fdb_entries']: mac_address = entry[0] if mac_address == '00:00:00:00:00:00': continue ip_address = entry[1] # create/get tunnel data if tunnel_name not in fdbs: fdbs[tunnel_name] = {} tunnel_fdbs = fdbs[tunnel_name] # update tunnel folder tunnel_fdbs['folder'] = folder # maybe create records for tunnel if 'records' not in tunnel_fdbs: tunnel_fdbs['records'] = {} # add entry to records map keyed by mac address tunnel_fdbs['records'][mac_address] = \ {'endpoint': vtep_info['vtep'], 'ip_address': ip_address} def update_bigip_fdb(self, bigip, fdb): # Update l2 records self.add_bigip_fdb(bigip, fdb) def remove_bigip_fdb(self, bigip, fdb): # Add L2 records for MAC addresses behind tunnel endpoints for fdb_operation in \ [{'network_type': 'vxlan', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.delete_fdb_entries}, {'network_type': 'gre', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.delete_fdb_entries}]: self._operate_bigip_fdb(bigip, fdb, fdb_operation) # Utilities def get_network_name(self, bigip, network): # This constructs a name for a tunnel or vlan interface preserve_network_name = False if network['id'] in self.conf.common_network_ids: network_name = self.conf.common_network_ids[network['id']] preserve_network_name = True elif network['provider:network_type'] == 'vlan': network_name = self.get_vlan_name(network, bigip.hostname) elif network['provider:network_type'] == 'flat': network_name = self.get_vlan_name(network, bigip.hostname) elif network['provider:network_type'] == 'vxlan': network_name = _get_tunnel_name(network) elif network['provider:network_type'] == 'gre': network_name = _get_tunnel_name(network) else: error_message = 'Unsupported network type %s.' \ % network['provider:network_type'] + \ ' Cannot setup selfip or snat.' LOG.error(error_message) raise f5_ex.InvalidNetworkType(error_message) return network_name, preserve_network_name
class L2ServiceBuilder(object): def __init__(self, conf, f5_global_routed_mode): self.conf = conf self.f5_global_routed_mode = f5_global_routed_mode self.vlan_binding = None self.fdb_connector = None self.vcmp_manager = None self.interface_mapping = {} self.tagging_mapping = {} self.system_helper = SystemHelper() self.network_helper = NetworkHelper() self.service_adapter = ServiceModelAdapter(conf) if not f5_global_routed_mode: self.fdb_connector = FDBConnectorML2(conf) if self.conf.vlan_binding_driver: try: self.vlan_binding = importutils.import_object( self.conf.vlan_binding_driver, self.conf, self) except ImportError: LOG.error('Failed to import VLAN binding driver: %s' % self.conf.vlan_binding_driver) # map format is phynet:interface:tagged for maps in self.conf.f5_external_physical_mappings: intmap = maps.split(':') net_key = str(intmap[0]).strip() if len(intmap) > 3: net_key = net_key + ':' + str(intmap[3]).strip() self.interface_mapping[net_key] = str(intmap[1]).strip() self.tagging_mapping[net_key] = str(intmap[2]).strip() LOG.debug('physical_network %s = interface %s, tagged %s' % (net_key, intmap[1], intmap[2])) def post_init(self): if self.vlan_binding: LOG.debug('Getting BIG-IP device interface for VLAN Binding') self.vlan_binding.register_bigip_interfaces() def tunnel_sync(self, tunnel_ips): if self.fdb_connector: self.fdb_connector.advertise_tunnel_ips(tunnel_ips) def set_tunnel_rpc(self, tunnel_rpc): # Provide FDB Connector with ML2 RPC access if self.fdb_connector: self.fdb_connector.set_tunnel_rpc(tunnel_rpc) def set_l2pop_rpc(self, l2pop_rpc): # Provide FDB Connector with ML2 RPC access if self.fdb_connector: self.fdb_connector.set_l2pop_rpc(l2pop_rpc) def set_context(self, context): if self.fdb_connector: self.fdb_connector.set_context(context) def is_common_network(self, network): # Does this network belong in the /Common folder? return network['shared'] or \ (network['id'] in self.conf.common_network_ids) or \ ('router:external' in network and network['router:external'] and self.conf.f5_common_external_networks) def get_vlan_name(self, network, hostname): segment = self._get_segment(network, 'vlan') # Construct a consistent vlan name net_key = segment['provider:physical_network'] # look for host specific interface mapping if net_key + ':' + hostname in self.interface_mapping: interface = self.interface_mapping[net_key + ':' + hostname] tagged = self.tagging_mapping[net_key + ':' + hostname] # look for specific interface mapping elif net_key in self.interface_mapping: interface = self.interface_mapping[net_key] tagged = self.tagging_mapping[net_key] # use default mapping else: interface = self.interface_mapping['default'] tagged = self.tagging_mapping['default'] if tagged: vlanid = segment['provider:segmentation_id'] else: vlanid = 0 # vlan_name = "vlan-" + \ # str(interface).replace(".", "-") + \ # "-" + str(vlanid) # if len(vlan_name) > 15: # vlan_name = 'vlan-tr-' + str(vlanid) vlan_name = 'vlan-' + network['id'][0:10] return vlan_name def assure_bigip_network(self, bigip, network): segment = self._get_segment(network, 'vlan') # Ensure bigip has configured network object if not network: LOG.error(' assure_bigip_network: ' 'Attempted to assure a network with no id..skipping.') return if network['id'] in bigip.assured_networks: return if network['id'] in self.conf.common_network_ids: LOG.debug(' assure_bigip_network: ' 'Network is a common global network... skipping.') return LOG.debug(" assure_bigip_network network: %s" % str(network)) start_time = time() if self.is_common_network(network): network_folder = 'Common' else: network_folder = self.service_adapter.get_folder_name( network['tenant_id']) # setup all needed L2 network segments if segment['provider:network_type'] == 'flat': self._assure_device_network_flat(network, bigip, network_folder) elif segment['provider:network_type'] == 'vlan': self._assure_device_network_vlan(network, bigip, network_folder) elif segment['provider:network_type'] == 'vxlan': self._assure_device_network_vxlan(network, bigip, network_folder) elif segment['provider:network_type'] == 'gre': self._assure_device_network_gre(network, bigip, network_folder) else: error_message = 'Unsupported network type %s.' \ % segment['provider:network_type'] + \ ' Cannot setup network.' LOG.error(error_message) raise f5ex.InvalidNetworkType(error_message) bigip.assured_networks.append(network['id']) if time() - start_time > .001: LOG.debug(" assure bigip network took %.5f secs" % (time() - start_time)) def _assure_device_network_flat(self, network, bigip, network_folder): segment = self._get_segment(network, 'vlan') # Ensure bigip has configured flat vlan (untagged) interface = self.interface_mapping['default'] vlanid = 0 # Do we have host specific mappings? net_key = segment['provider:physical_network'] if net_key + ':' + bigip.hostname in \ self.interface_mapping: interface = self.interface_mapping[net_key + ':' + bigip.hostname] # Do we have a mapping for this network elif net_key in self.interface_mapping: interface = self.interface_mapping[net_key] vlan_name = self.get_vlan_name(network, bigip.hostname) # TODO(Rich Browne): Implementation with VCMP self._assure_vcmp_device_network(bigip, vlan={ 'name': vlan_name, 'folder': network_folder, 'id': vlanid, 'interface': interface, 'network': network }) if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip): interface = None model = { 'name': vlan_name, 'interface': interface, 'partition': network_folder, 'description': network['id'], 'route_domain_id': network['route_domain_id'] } self.network_helper.create_vlan(bigip, model) def _assure_device_network_vlan(self, network, bigip, network_folder): segment = self._get_segment(network, 'vlan') # Ensure bigip has configured tagged vlan # VLAN names are limited to 64 characters including # the folder name, so we name them foolish things. interface = self.interface_mapping['default'] tagged = self.tagging_mapping['default'] # Do we have host specific mappings? net_key = segment['provider:physical_network'] if net_key + ':' + bigip.hostname in \ self.interface_mapping: interface = self.interface_mapping[net_key + ':' + bigip.hostname] tagged = self.tagging_mapping[net_key + ':' + bigip.hostname] # Do we have a mapping for this network elif net_key in self.interface_mapping: LOG.error('*****************') interface = self.interface_mapping[net_key] tagged = self.tagging_mapping[net_key] LOG.error(interface) LOG.error(tagged) if tagged: vlanid = segment['provider:segmentation_id'] else: vlanid = 0 vlan_name = self.get_vlan_name(network, bigip.hostname) self._assure_vcmp_device_network(bigip, vlan={ 'name': vlan_name, 'folder': network_folder, 'id': vlanid, 'interface': interface, 'network': network }) if self.vcmp_manager and self.vcmp_manager.get_vcmp_host(bigip): interface = None model = { 'name': vlan_name, 'interface': interface, 'tag': vlanid, 'partition': network_folder, 'description': network['id'], 'route_domain_id': network['route_domain_id'] } LOG.error('*****************') LOG.error(network) LOG.error(model) self.network_helper.create_vlan(bigip, model) if self.vlan_binding: self.vlan_binding.allow_vlan(device_name=bigip.device_name, interface=interface, vlanid=vlanid) def _assure_device_network_vxlan(self, network, bigip, partition): # Ensure bigip has configured vxlan if not bigip.local_ip: error_message = 'Cannot create tunnel %s on %s' \ % (network['id'], bigip.hostname) error_message += ' no VTEP SelfIP defined.' LOG.error('VXLAN:' + error_message) raise f5ex.MissingVTEPAddress('VXLAN:' + error_message) tunnel_name = _get_tunnel_name(network) # create the main tunnel entry for the fdb records payload = { 'name': tunnel_name, 'partition': partition, 'profile': 'vxlan_ovs', 'key': network['provider:segmentation_id'], 'localAddress': bigip.local_ip, 'description': network['id'], 'route_domain_id': network['route_domain_id'] } LOG.debug("---Creating VXLAN network---") LOG.debug(payload) self.network_helper.create_multipoint_tunnel(bigip, payload) LOG.debug("---VXLAN network Created---") if self.fdb_connector: self.fdb_connector.notify_vtep_added(network, bigip.local_ip) def _assure_device_network_gre(self, network, bigip, partition): # Ensure bigip has configured gre tunnel if not bigip.local_ip: error_message = 'Cannot create tunnel %s on %s' \ % (network['id'], bigip.hostname) error_message += ' no VTEP SelfIP defined.' LOG.error('L2GRE:' + error_message) raise f5ex.MissingVTEPAddress('L2GRE:' + error_message) tunnel_name = _get_tunnel_name(network) payload = { 'name': tunnel_name, 'partition': partition, 'profile': 'gre_ovs', 'key': network['provider:segmentation_id'], 'localAddress': bigip.local_ip, 'description': network['id'], 'route_domain_id': network['route_domain_id'] } self.network_helper.create_multipoint_tunnel(bigip, payload) if self.fdb_connector: self.fdb_connector.notify_vtep_added(network, bigip.local_ip) def _is_vlan_assoc_with_vcmp_guest(self, bigip, vlan): if not self.vcmp_manager: return False # Is a vlan associated with a vcmp_guest? try: vcmp_host = self.vcmp_manager.get_vcmp_host(bigip) vcmp_guest = self.vcmp_manager.get_vcmp_guest(vcmp_host, bigip) vlan_list = vcmp_host['bigip'].system.sys_vcmp.get_vlan( [vcmp_guest['name']]) full_path_vlan_name = '/Common/' + prefixed(vlan['name']) if full_path_vlan_name in vlan_list[0]: LOG.debug(('VLAN %s is associated with guest %s' % (full_path_vlan_name, vcmp_guest['mgmt_addr']))) return True except Exception as exc: LOG.error(('Exception checking association of VLAN %s ' 'to vCMP Guest %s: %s ' % (vlan['name'], vcmp_guest['mgmt_addr'], exc))) return False LOG.debug(('VLAN %s is not associated with guest %s' % (full_path_vlan_name, vcmp_guest['mgmt_addr']))) return False def _assure_vcmp_device_network(self, bigip, vlan): # REVISIT FOR VCMP SUPPORT # For vCMP Guests, add VLAN to vCMP Host, associate VLAN with # vCMP Guest, and remove VLAN from /Common on vCMP Guest. if not self.vcmp_manager: return vcmp_host = self.vcmp_manager.get_vcmp_host(bigip) if not vcmp_host: return # Create the VLAN on the vCMP Host try: model = { 'name': vlan['name'], 'partition': '/Common', 'tag': vlan['id'], 'interface': vlan['interface'], 'description': vlan['network']['id'], 'route_domain_id': vlan['network']['route_domain_id'] } self.network_helper.create_vlan(bigip, model) LOG.debug(('Created VLAN %s on vCMP Host %s' % (vlan['name'], vcmp_host['bigip'].hostname))) except Exception as exc: LOG.error(('Exception creating VLAN %s on vCMP Host %s:%s' % (vlan['name'], vcmp_host['bigip'].hostname, exc))) # Determine if the VLAN is already associated with the vCMP Guest if self._is_vlan_assoc_with_vcmp_guest(bigip, vlan): return # Associate the VLAN with the vCMP Guest # MSG: bigip.system does not exist vcmp_guest = self.vcmp_manager.get_vcmp_guest(vcmp_host, bigip) try: vlan_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\ create('Common.StringSequence') vlan_seq.values = prefixed(vlan['name']) vlan_seq_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\ create('Common.StringSequenceSequence') vlan_seq_seq.values = [vlan_seq] vcmp_host['bigip'].system.sys_vcmp.add_vlan([vcmp_guest['name']], vlan_seq_seq) LOG.debug(('Associated VLAN %s with vCMP Guest %s' % (vlan['name'], vcmp_guest['mgmt_addr']))) except Exception as exc: LOG.error(('Exception associating VLAN %s to vCMP Guest %s: %s ' % (vlan['name'], vcmp_guest['mgmt_addr'], exc))) # Wait for the VLAN to propagate to /Common on vCMP Guest full_path_vlan_name = '/Common/' + prefixed(vlan['name']) vlan_created = False v = bigip.net.vlans.vlan try: for _ in range(0, 30): if v.exists(name=vlan['name'], partition='/Common'): v.load(name=vlan['name'], partition='/Common') vlan_created = True break LOG.debug(('Wait for VLAN %s to be created on vCMP Guest %s.' % (full_path_vlan_name, vcmp_guest['mgmt_addr']))) # sleep(1) if vlan_created: LOG.debug(('VLAN %s exists on vCMP Guest %s.' % (full_path_vlan_name, vcmp_guest['mgmt_addr']))) else: LOG.error(('VLAN %s does not exist on vCMP Guest %s.' % (full_path_vlan_name, vcmp_guest['mgmt_addr']))) except Exception as exc: LOG.error(('Exception waiting for vCMP Host VLAN %s to ' 'be created on vCMP Guest %s: %s' % (vlan['name'], vcmp_guest['mgmt_addr'], exc))) # Delete the VLAN from the /Common folder on the vCMP Guest if vlan_created: try: v.delete() LOG.debug(('Deleted VLAN %s from vCMP Guest %s' % (full_path_vlan_name, vcmp_guest['mgmt_addr']))) except Exception as exc: LOG.error( ('Exception deleting VLAN %s from vCMP Guest %s: %s' % (full_path_vlan_name, vcmp_guest['mgmt_addr'], exc))) def delete_bigip_network(self, bigip, network): segment = self._get_segment(network, 'vlan') # Delete network on bigip if network['id'] in self.conf.common_network_ids: LOG.debug('skipping delete of common network %s' % network['id']) return if self.is_common_network(network): network_folder = 'Common' else: network_folder = self.service_adapter.get_folder_name( network['tenant_id']) if segment['provider:network_type'] == 'vlan': self._delete_device_vlan(bigip, network, network_folder) elif segment['provider:network_type'] == 'flat': self._delete_device_flat(bigip, network, network_folder) elif segment['provider:network_type'] == 'vxlan': self._delete_device_vxlan(bigip, network, network_folder) elif segment['provider:network_type'] == 'gre': self._delete_device_gre(bigip, network, network_folder) else: LOG.error('Unsupported network type %s. Can not delete.' % network['provider:network_type']) if network['id'] in bigip.assured_networks: bigip.assured_networks.remove(network['id']) def _delete_device_vlan(self, bigip, network, network_folder): # Delete tagged vlan on specific bigip vlan_name = self.get_vlan_name(network, bigip.hostname) self.network_helper.delete_vlan(bigip, vlan_name, partition=network_folder) if self.vlan_binding: interface = self.interface_mapping['default'] tagged = self.tagging_mapping['default'] vlanid = 0 # Do we have host specific mappings? net_key = network['provider:physical_network'] if net_key + ':' + bigip.hostname in \ self.interface_mapping: interface = self.interface_mapping[net_key + ':' + bigip.hostname] tagged = self.tagging_mapping[net_key + ':' + bigip.hostname] # Do we have a mapping for this network elif net_key in self.interface_mapping: interface = self.interface_mapping[net_key] tagged = self.tagging_mapping[net_key] if tagged: vlanid = network['provider:segmentation_id'] else: vlanid = 0 self.vlan_binding.prune_vlan(device_name=bigip.device_name, interface=interface, vlanid=vlanid) self._delete_vcmp_device_network(bigip, vlan_name) def _delete_device_flat(self, bigip, network, network_folder): # Delete untagged vlan on specific bigip vlan_name = self.get_vlan_name(network, bigip.hostname) self.network_helper.delete_vlan(bigip, vlan_name, partition=network_folder) self._delete_vcmp_device_network(bigip, vlan_name) def _delete_device_vxlan(self, bigip, network, network_folder): # Delete vxlan tunnel on specific bigip tunnel_name = _get_tunnel_name(network) self.network_helper.delete_all_fdb_entries(bigip, tunnel_name, partition=network_folder) self.network_helper.delete_tunnel(bigip, tunnel_name, partition=network_folder) if self.fdb_connector: self.fdb_connector.notify_vtep_removed(network, bigip.local_ip) def _delete_device_gre(self, bigip, network, network_folder): # Delete gre tunnel on specific bigip tunnel_name = _get_tunnel_name(network) self.network_helper.delete_all_fdb_entries(bigip, tunnel_name, partition=network_folder) self.network_helper.delete_tunnel(bigip, tunnel_name, partition=network_folder) if self.fdb_connector: self.fdb_connector.notify_vtep_removed(network, bigip.local_ip) def _delete_vcmp_device_network(self, bigip, vlan_name): # For vCMP Guests, disassociate VLAN from vCMP Guest and # delete VLAN from vCMP Host. if not self.vcmp_manager: return vcmp_host = self.vcmp_manager.get_vcmp_host(bigip) if not vcmp_host: return # Remove VLAN association from the vCMP Guest vcmp_guest = self.vcmp_manager.get_vcmp_guest(vcmp_host, bigip) try: # REVISIT: the extra attributes in vcmp bigips need to be # worked on. vlan_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\ create('Common.StringSequence') vlan_seq.values = prefixed(vlan_name) vlan_seq_seq = vcmp_host['bigip'].system.sys_vcmp.typefactory.\ create('Common.StringSequenceSequence') vlan_seq_seq.values = [vlan_seq] vcmp_host['bigip'].system.sys_vcmp.remove_vlan( [vcmp_guest['name']], vlan_seq_seq) LOG.debug(('Removed VLAN %s association from vCMP Guest %s' % (vlan_name, vcmp_guest['mgmt_addr']))) except Exception as exc: LOG.error( ('Exception removing VLAN %s association from vCMP ' 'Guest %s:%s' % (vlan_name, vcmp_guest['mgmt_addr'], exc))) # Only delete VLAN if it is not in use by other vCMP Guests if self.vcmp_manager.get_vlan_use_count(vcmp_host, vlan_name): LOG.debug(('VLAN %s in use by other vCMP Guests on vCMP Host %s' % (vlan_name, vcmp_host['bigip'].hostname))) return # Delete VLAN from vCMP Host. This will fail if any other vCMP Guest # is using this VLAN try: self.network_helper.delete_vlan(vcmp_host['bigip'], vlan_name) LOG.debug(('Deleted VLAN %s from vCMP Host %s' % (vlan_name, vcmp_host['bigip'].hostname))) except Exception as exc: LOG.error(('Exception deleting VLAN %s from vCMP Host %s:%s' % (vlan_name, vcmp_host['bigip'].icontrol.hostname, exc))) def add_bigip_fdbs(self, bigip, net_folder, fdb_info, vteps_by_type): # Add fdb records for a mac/ip with specified vteps network = fdb_info['network'] net_type = network['provider:network_type'] vteps_key = net_type + '_vteps' if vteps_key in vteps_by_type: vteps = vteps_by_type[vteps_key] if net_type == 'gre': self.add_gre_fdbs(bigip, net_folder, fdb_info, vteps) elif net_type == 'vxlan': self.add_vxlan_fdbs(bigip, net_folder, fdb_info, vteps) def add_gre_fdbs(self, bigip, net_folder, fdb_info, vteps): # Add gre fdb records network = fdb_info['network'] ip_address = fdb_info['ip_address'] mac_address = fdb_info['mac_address'] tunnel_name = _get_tunnel_name(network) for vtep in vteps: if mac_address: mac_addr = mac_address else: mac_addr = _get_tunnel_fake_mac(network, vtep) # REVISIT(Rich Browne) caller must provide folder and not tenant_id self.network_helper.add_fdb_entry(bigip, tunnel_name=tunnel_name, partition=net_folder, mac_address=mac_addr, vtep_ip_address=vtep, arp_ip_address=ip_address) def add_vxlan_fdbs(self, bigip, net_folder, fdb_info, vteps): # Add vxlan fdb records network = fdb_info['network'] ip_address = fdb_info['ip_address'] mac_address = fdb_info['mac_address'] tunnel_name = _get_tunnel_name(network) for vtep in vteps: if mac_address: mac_addr = mac_address else: mac_addr = _get_tunnel_fake_mac(network, vtep) # REVISIT caller must provide folder and not tenant_id self.network_helper.add_fdb_entry(bigip, tunnel_name=tunnel_name, partition=net_folder, mac_address=mac_addr, vtep_ip_address=vtep, arp_ip_address=ip_address) def delete_bigip_fdbs(self, bigip, net_folder, fdb_info, vteps_by_type): # Delete fdb records for a mac/ip with specified vteps network = fdb_info['network'] net_type = network['provider:network_type'] vteps_key = net_type + '_vteps' if vteps_key in vteps_by_type: vteps = vteps_by_type[vteps_key] if net_type == 'gre': self.delete_gre_fdbs(bigip, net_folder, fdb_info, vteps) elif net_type == 'vxlan': self.delete_vxlan_fdbs(bigip, net_folder, fdb_info, vteps) def delete_gre_fdbs(self, bigip, net_folder, fdb_info, vteps): # delete gre fdb records network = fdb_info['network'] ip_address = fdb_info['ip_address'] mac_address = fdb_info['mac_address'] tunnel_name = _get_tunnel_name(network) for vtep in vteps: if mac_address: mac_addr = mac_address else: mac_addr = _get_tunnel_fake_mac(network, vtep) self.network_helper.delete_fdb_entry(bigip, tunnel_name=tunnel_name, mac_address=mac_addr, arp_ip_address=ip_address, partition=net_folder) def delete_vxlan_fdbs(self, bigip, net_folder, fdb_info, vteps): # delete vxlan fdb records network = fdb_info['network'] ip_address = fdb_info['ip_address'] mac_address = fdb_info['mac_address'] tunnel_name = _get_tunnel_name(network) for vtep in vteps: if mac_address: mac_addr = mac_address else: mac_addr = _get_tunnel_fake_mac(network, vtep) self.network_helper.delete_fdb_entry(bigip, tunnel_name=tunnel_name, mac_address=mac_addr, arp_ip_address=ip_address, partition=net_folder) def add_bigip_fdb(self, bigip, fdb): # Add entries from the fdb relevant to the bigip for fdb_operation in \ [{'network_type': 'vxlan', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.add_fdb_entries}, {'network_type': 'gre', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.add_fdb_entries}]: self._operate_bigip_fdb(bigip, fdb, fdb_operation) def _operate_bigip_fdb(self, bigip, fdb, fdb_operation): """Add L2 records for MAC addresses behind tunnel endpoints. Description of fdb structure: {'<network_id>': 'segment_id': <int> 'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ] '<network_id>': 'segment_id': 'ports': [ '<vtep>': ['<mac_address>': '<ip_address>'] ] } Sample real fdb structure: {u'45bbbce1-191b-4f7b-84c5-54c6c8243bd2': {u'segment_id': 1008, u'ports': {u'10.30.30.2': [[u'00:00:00:00:00:00', u'0.0.0.0'], [u'fa:16:3e:3d:7b:7f', u'10.10.1.4']]}, u'network_type': u'vxlan'}} """ network_type = fdb_operation['network_type'] get_tunnel_folder = fdb_operation['get_tunnel_folder'] fdb_method = fdb_operation['fdb_method'] for network in fdb: net_fdb = fdb[network] if net_fdb['network_type'] == network_type: net = { 'name': network, 'provider:network_type': net_fdb['network_type'], 'provider:segmentation_id': net_fdb['segment_id'] } tunnel_name = _get_tunnel_name(net) folder = get_tunnel_folder(bigip, tunnel_name=tunnel_name) net_info = { 'network': network, 'folder': folder, 'tunnel_name': tunnel_name, 'net_fdb': net_fdb } fdbs = self._get_bigip_network_fdbs(bigip, net_info) if len(fdbs) > 0: fdb_method(fdb_entries=fdbs) def _get_bigip_network_fdbs(self, bigip, net_info): # Get network fdb entries to add to a bigip if not net_info['folder']: return {} net_fdb = net_info['net_fdb'] fdbs = {} for vtep in net_fdb['ports']: # bigip does not need to set fdb entries for local addresses if vtep == bigip.local_ip: continue # most net_info applies to the vtep vtep_info = dict(net_info) # but the network fdb is too broad so delete it del vtep_info['net_fdb'] # use a slice of the fdb for the vtep instead vtep_info['vtep'] = vtep vtep_info['fdb_entries'] = net_fdb['ports'][vtep] self._merge_vtep_fdbs(vtep_info, fdbs) return fdbs def _merge_vtep_fdbs(self, vtep_info, fdbs): # Add L2 records for a specific network+vtep folder = vtep_info['folder'] tunnel_name = vtep_info['tunnel_name'] for entry in vtep_info['fdb_entries']: mac_address = entry[0] if mac_address == '00:00:00:00:00:00': continue ip_address = entry[1] # create/get tunnel data if tunnel_name not in fdbs: fdbs[tunnel_name] = {} tunnel_fdbs = fdbs[tunnel_name] # update tunnel folder tunnel_fdbs['folder'] = folder # maybe create records for tunnel if 'records' not in tunnel_fdbs: tunnel_fdbs['records'] = {} # add entry to records map keyed by mac address tunnel_fdbs['records'][mac_address] = \ {'endpoint': vtep_info['vtep'], 'ip_address': ip_address} def update_bigip_fdb(self, bigip, fdb): # Update l2 records self.add_bigip_fdb(bigip, fdb) def remove_bigip_fdb(self, bigip, fdb): # Add L2 records for MAC addresses behind tunnel endpoints for fdb_operation in \ [{'network_type': 'vxlan', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.delete_fdb_entries}, {'network_type': 'gre', 'get_tunnel_folder': self.network_helper.get_tunnel_folder, 'fdb_method': self.network_helper.delete_fdb_entries}]: self._operate_bigip_fdb(bigip, fdb, fdb_operation) # Utilities def get_network_name(self, bigip, network): segment = self._get_segment(network, 'vlan') # This constructs a name for a tunnel or vlan interface preserve_network_name = False if network['id'] in self.conf.common_network_ids: network_name = self.conf.common_network_ids[network['id']] preserve_network_name = True elif segment['provider:network_type'] == 'vlan': network_name = self.get_vlan_name(network, bigip.hostname) elif segment['provider:network_type'] == 'flat': network_name = self.get_vlan_name(network, bigip.hostname) elif segment['provider:network_type'] == 'vxlan': network_name = _get_tunnel_name(network) elif segment['provider:network_type'] == 'gre': network_name = _get_tunnel_name(network) else: error_message = 'Unsupported network type %s.' \ % network['provider:network_type'] + \ ' Cannot setup selfip or snat.' LOG.error(error_message) raise f5ex.InvalidNetworkType(error_message) return network_name, preserve_network_name def _get_segment(self, network, segment_type): segments = network['segments'] for segment in segments: LOG.error(_(segment)) if segment['provider:network_type'] == segment_type: LOG.error("Returning segment") LOG.error(_(segment)) return segment