def __init__(self, conf, driver): # XXX maybe we need a better name: conf """Create a BigipTenantManager.""" self.conf = conf self.driver = driver self.system_helper = SystemHelper() self.network_helper = NetworkHelper() self.service_adapter = self.driver.service_adapter
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 test_network_helper_delete_fdb_entries( self, l2_service, service, bigips): # delete member fdb entry loadbalancer = None members = list() member = service['members'][1] network_id = member['network_id'] member['network'] = service['networks'][network_id] members.append(member) tunnel_records = l2_service.create_fdb_records(loadbalancer, members) network_helper = NetworkHelper() tunnel = mock.MagicMock() tunnel.exists = mock.MagicMock(return_value=True) tunnel_record = mock.MagicMock() tunnel.records_s.records.load = mock.MagicMock( return_value=tunnel_record) tunnel.records_s.records.exist = mock.MagicMock(return_value=True) bigip = bigips[0] bigip.tm.net.fdb.tunnels.tunnel.load = mock.MagicMock( return_value=tunnel) network_helper.delete_fdb_entries(bigip, fdb_entries=tunnel_records) # expect to delete tunnel_record.delete.assert_called()
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]))
class DisconnectedService(object): network_name = "disconnected_network" def __init__(self): self.network_name = DisconnectedService.network_name self.network_helper = NetworkHelper() # The following method presumes that the plugin driver is aware that we're # running in hierarchical mode or not and sets segmentation_id correctly. def is_service_connected(self, service): networks = service["networks"] network_id = service["loadbalancer"]["network_id"] network = networks.get(network_id, {}) if network.get("provider:network_type", "") == "flat": return True segmentation_id = network.get("provider:segmentation_id", 0) return segmentation_id def is_virtual_connected(self, virtual, bigips): # check if virtual_server is connected on any of our bigips connected = True for bigip in bigips: vsf = bigip.tm.ltm.virtuals.virtual if vsf.exists(name=virtual["name"], partition=virtual["partition"]): vs = vsf.load(name=virtual["name"], partition=virtual["partition"]) if getattr(vs, "vlansDisabled", False) or not getattr(vs, "vlansEnabled", True): # accommodate quirk of how big-ip returns virtual server # if vlans are disabled OR vlans are not enabled, then # we're connected continue network_path = "/%s/%s" % (virtual["partition"], self.network_name) if network_path in getattr(vs, "vlans", []): connected = False break return connected def network_exists(self, bigip, partition): t = bigip.tm.net.tunnels.tunnels.tunnel return t.exists(name=self.network_name, partition=partition) @log_helpers.log_method_call def create_network(self, bigip, partition): model = { "name": self.network_name, "partition": partition, "profile": "ppp", "description": "Tenant disconnected network", } t = self.network_helper.create_tunnel(bigip, model) return t @log_helpers.log_method_call def delete_network(self, bigip, partition): tf = bigip.tm.net.tunnels.tunnels.tunnel if tf.exists(name=self.network_name, partition=partition): tf.load(name=self.network_name, partition=partition).delete()
def __init__(self, driver, l2_service, l3_binding): self.driver = driver self.l2_service = l2_service self.l3_binding = l3_binding self.snatpool_manager = BigIPResourceHelper(ResourceType.snatpool) self.snat_translation_manager = BigIPResourceHelper( ResourceType.snat_translation) self.network_helper = NetworkHelper()
def purge_folder_contents(self, bigip, folder): network_helper = NetworkHelper() if folder not in self.exempt_folders: # First remove all LTM resources. ltm_types = [ ResourceType.virtual, ResourceType.virtual_address, ResourceType.pool, ResourceType.http_monitor, ResourceType.https_monitor, ResourceType.tcp_monitor, ResourceType.ping_monitor, ResourceType.node, ResourceType.snat, ResourceType.snatpool, ResourceType.snat_translation, ResourceType.universal_persistence, ResourceType.rule, ResourceType.l7policy ] for ltm_type in ltm_types: resource = BigIPResourceHelper(ltm_type) [r.delete() for r in resource.get_resources(bigip, folder)] # Remove all net resources net_types = [ ResourceType.arp, ResourceType.selfip, ResourceType.vlan, ResourceType.route_domain ] for net_type in net_types: resource = BigIPResourceHelper(net_type) [r.delete() for r in resource.get_resources(bigip, folder)] # Tunnels and fdb's require some special attention. resource = BigIPResourceHelper(ResourceType.tunnel) tunnels = resource.get_resources(bigip, folder) for tunnel in tunnels: network_helper.delete_all_fdb_entries( bigip, tunnel.name, folder) network_helper.delete_tunnel( bigip, tunnel.name, folder)
def __init__(self, f5_global_routed_mode, conf, driver, l3_binding=None): self.f5_global_routed_mode = f5_global_routed_mode self.conf = conf self.driver = driver self.l3_binding = l3_binding self.l2_service = L2ServiceBuilder(driver, f5_global_routed_mode) self.bigip_selfip_manager = BigipSelfIpManager(self.driver, self.l2_service, self.driver.l3_binding) self.bigip_snat_manager = BigipSnatManager(self.driver, self.l2_service, self.driver.l3_binding) self.rds_cache = {} self.interface_mapping = self.l2_service.interface_mapping self.network_helper = NetworkHelper() self.service_adapter = self.driver.service_adapter
class DisconnectedService(object): network_name = 'disconnected_network' def __init__(self): self.network_name = DisconnectedService.network_name self.network_helper = NetworkHelper() # The following method presumes that the plugin driver is aware that we're # running in hierarchical mode or not and sets segmentation_id correctly. def is_service_connected(self, service): networks = service['networks'] network_id = service['loadbalancer']['network_id'] segmentation_id = networks[network_id]['provider:segmentation_id'] return (segmentation_id) def is_virtual_connected(self, virtual, bigips): # check if virtual_server is connected on any of our bigips connected = True for bigip in bigips: vsf = bigip.tm.ltm.virtuals.virtual if vsf.exists(name=virtual['name'], partition=virtual['partition']): vs = vsf.load(name=virtual['name'], partition=virtual['partition']) if (getattr(vs, 'vlansDisabled', False) or not getattr(vs, 'vlansEnabled', True)): # accommodate quirk of how big-ip returns virtual server # if vlans are disabled OR vlans are not enabled, then # we're connected continue network_path = "/%s/%s" % (virtual['partition'], self.network_name) if network_path in getattr(vs, 'vlans', []): connected = False break return connected def network_exists(self, bigip, partition): t = bigip.tm.net.tunnels.tunnels.tunnel return t.exists(name=self.network_name, partition=partition) @log_helpers.log_method_call def create_network(self, bigip, partition): model = { 'name': self.network_name, 'partition': partition, 'profile': 'ppp', 'description': 'Tenant disconnected network' } t = self.network_helper.create_tunnel(bigip, model) return t @log_helpers.log_method_call def delete_network(self, bigip, partition): tf = bigip.tm.net.tunnels.tunnels.tunnel if tf.exists(name=self.network_name, partition=partition): tf.load(name=self.network_name, partition=partition).delete()
def test_add_remove_fdbs(bigip, icontrol_driver): """ Test simulating L2 pop events to add/remove fdb entries.""" net_helper = NetworkHelper() tunnels = list() fdb_entries = list() seg_id_start = 167 seg_id_end = 176 n_records = 9 # create tunnels on BIG-IP, and fake fdb entries for seg_id in range(seg_id_start, seg_id_end): tunnel_name = 'tunnel-vxlan-{}'.format(seg_id) model = { 'name': tunnel_name, 'key': seg_id, 'profile': 'vxlan_ovs', 'localAddress': '201.0.155.10' } net_helper.create_multipoint_tunnel(bigip.bigip, model) tunnels.append(tunnel_name) # create a set of fdb entries that reference network seg ID for _ in range(n_records): entry = create_fdb_entry(seg_id) fdb_entries.append(entry) # add fdb entries for fdb_entry in fdb_entries: # mimic neutron L2 pop add_fdb_entries icontrol_driver.fdb_add(fdb_entry) for fdb_entry in fdb_entries: # mimic neutron L2 pop add_fdb_entries icontrol_driver.fdb_add(fdb_entry) # check created for tunnel_name in tunnels: records = net_helper.get_fdb_entry(bigip.bigip, tunnel_name=tunnel_name) assert records # remove fdb entries for fdb_entry in fdb_entries: # mimic neutron L2 pop remove_fdb_entries icontrol_driver.fdb_remove(fdb_entry) # check removed for tunnel_name in tunnels: records = net_helper.get_fdb_entry(bigip.bigip, tunnel_name=tunnel_name) assert not records net_helper.delete_tunnel(bigip.bigip, tunnel_name)
class DisconnectedService(object): network_name = 'disconnected_network' def __init__(self): self.network_name = DisconnectedService.network_name self.network_helper = NetworkHelper() # The following method presumes that the plugin driver is aware that we're # running in hierarchical mode or not and sets segmentation_id correctly. def is_service_connected(self, service): networks = service['networks'] network_id = service['loadbalancer']['network_id'] segmentation_id = networks[network_id]['provider:segmentation_id'] return (segmentation_id) def is_virtual_connected(self, virtual, bigips): # check if virtual_server is connected on any of our bigips connected = True for bigip in bigips: vsf = bigip.tm.ltm.virtuals.virtual if vsf.exists(name=virtual['name'], partition=virtual['partition']): vs = vsf.load( name=virtual['name'], partition=virtual['partition']) if (getattr(vs, 'vlansDisabled', False) or not getattr(vs, 'vlansEnabled', True)): # accommodate quirk of how big-ip returns virtual server # if vlans are disabled OR vlans are not enabled, then # we're connected continue network_path = "/%s/%s" % (virtual['partition'], self.network_name) if network_path in getattr(vs, 'vlans', []): connected = False break return connected def network_exists(self, bigip, partition): t = bigip.tm.net.tunnels.tunnels.tunnel return t.exists(name=self.network_name, partition=partition) @log_helpers.log_method_call def create_network(self, bigip, partition): model = {'name': self.network_name, 'partition': partition, 'profile': 'ppp', 'description': 'Tenant disconnected network'} t = self.network_helper.create_tunnel(bigip, model) return t @log_helpers.log_method_call def delete_network(self, bigip, partition): tf = bigip.tm.net.tunnels.tunnels.tunnel if tf.exists(name=self.network_name, partition=partition): tf.load(name=self.network_name, partition=partition).delete()
def purge_folder_contents(self, bigip, folder): network_helper = NetworkHelper() if folder not in self.exempt_folders: # First remove all LTM resources. ltm_types = [ ResourceType.virtual, ResourceType.pool, ResourceType.http_monitor, ResourceType.https_monitor, ResourceType.tcp_monitor, ResourceType.ping_monitor, ResourceType.node, ResourceType.snat, ResourceType.snatpool, ResourceType.snat_translation, ResourceType.rule ] for ltm_type in ltm_types: resource = BigIPResourceHelper(ltm_type) [r.delete() for r in resource.get_resources(bigip, folder)] # Remove all net resources net_types = [ ResourceType.arp, ResourceType.selfip, ResourceType.vlan, ResourceType.route_domain ] for net_type in net_types: resource = BigIPResourceHelper(net_type) [r.delete() for r in resource.get_resources(bigip, folder)] # Tunnels and fdb's require some special attention. resource = BigIPResourceHelper(ResourceType.tunnel) tunnels = resource.get_resources(bigip, folder) for tunnel in tunnels: network_helper.delete_all_fdb_entries(bigip, tunnel.name, folder) network_helper.delete_tunnel(bigip, tunnel.name, folder)
def test_network_helper_delete_fdb_entries( self, l2_service, service, bigips): # delete member fdb entry loadbalancer = None members = list() member = service['members'][1] network_id = member['network_id'] member['network'] = service['networks'][network_id] members.append(member) tunnel_records = l2_service.create_fdb_records(loadbalancer, members) network_helper = NetworkHelper() fdb_entry = mock.MagicMock() fdb_entry.modify = mock.MagicMock() bigip = bigips[0] bigip.tm.net.fdb.tunnels.tunnel.load = mock.MagicMock( return_value=fdb_entry) network_helper.delete_fdb_entries(bigip, fdb_entries=tunnel_records) # expect to modify with no records (i.e, removing entry) fdb_entry.modify.assert_called_with(records=None)
def test_network_helper_delete_fdb_entries(self, l2_service, service, bigips): # delete member fdb entry loadbalancer = None members = list() member = service['members'][1] network_id = member['network_id'] member['network'] = service['networks'][network_id] members.append(member) tunnel_records = l2_service.create_fdb_records(loadbalancer, members) network_helper = NetworkHelper() fdb_entry = mock.MagicMock() fdb_entry.modify = mock.MagicMock() bigip = bigips[0] bigip.tm.net.fdb.tunnels.tunnel.load = mock.MagicMock( return_value=fdb_entry) network_helper.delete_fdb_entries(bigip, fdb_entries=tunnel_records) # expect to modify with no records (i.e, removing entry) fdb_entry.modify.assert_called_with(records=None)
def test_network_helper_add_fdb_entries(self, l2_service, service, bigips): # add first member fdb entry loadbalancer = None members = list() member = service['members'][0] network_id = member['network_id'] member['network'] = service['networks'][network_id] members.append(member) tunnel_records = l2_service.create_fdb_records(loadbalancer, members) network_helper = NetworkHelper() fdb_entry = mock.MagicMock() fdb_entry.modify = mock.MagicMock() bigip = bigips[0] bigip.tm.net.fdb.tunnels.tunnel.load = mock.MagicMock( return_value=fdb_entry) network_helper.add_fdb_entries(bigip, fdb_entries=tunnel_records) # expect to modify with first member's VTEP and MAC addr fdb_entry.modify.assert_called_with( records=[{'endpoint': '192.168.130.59', 'name': 'fa:16:3e:0d:fa:c8'}]) # add second member fdb entry members = list() member = service['members'][1] network_id = member['network_id'] member['network'] = service['networks'][network_id] members.append(member) tunnel_records = l2_service.create_fdb_records(loadbalancer, members) network_helper.add_fdb_entries(bigip, fdb_entries=tunnel_records) # expect to modify with second member's VTEP and MAC addr fdb_entry.modify.assert_called_with( records=[{'endpoint': '192.168.130.60', 'name': 'fa:16:3e:0d:fa:c6'}])
def test_add_remove_fdbs(bigip, icontrol_driver): """ Test simulating L2 pop events to add/remove fdb entries.""" net_helper = NetworkHelper() tunnels = list() fdb_entries = list() seg_id_start = 167 seg_id_end = 176 n_records = 9 # create tunnels on BIG-IP, and fake fdb entries for seg_id in range(seg_id_start, seg_id_end): tunnel_name = 'tunnel-vxlan-{}'.format(seg_id) model = { 'name': tunnel_name, 'key': seg_id, 'profile': 'vxlan_ovs', 'localAddress': '201.0.155.10'} net_helper.create_multipoint_tunnel(bigip.bigip, model) tunnels.append(tunnel_name) # create a set of fdb entries that reference network seg ID for _ in range(n_records): entry = create_fdb_entry(seg_id) fdb_entries.append(entry) # add fdb entries for fdb_entry in fdb_entries: # mimic neutron L2 pop add_fdb_entries icontrol_driver.fdb_add(fdb_entry) for fdb_entry in fdb_entries: # mimic neutron L2 pop add_fdb_entries icontrol_driver.fdb_add(fdb_entry) # check created for tunnel_name in tunnels: records = net_helper.get_fdb_entry(bigip.bigip, tunnel_name=tunnel_name) assert records # remove fdb entries for fdb_entry in fdb_entries: # mimic neutron L2 pop remove_fdb_entries icontrol_driver.fdb_remove(fdb_entry) # check removed for tunnel_name in tunnels: records = net_helper.get_fdb_entry(bigip.bigip, tunnel_name=tunnel_name) assert not records net_helper.delete_tunnel(bigip.bigip, tunnel_name)
def __init__(self, f5_global_routed_mode, conf, driver, l3_binding=None): self.f5_global_routed_mode = f5_global_routed_mode self.conf = conf self.driver = driver self.l3_binding = l3_binding self.l2_service = L2ServiceBuilder(driver, f5_global_routed_mode) self.bigip_selfip_manager = BigipSelfIpManager( self.driver, self.l2_service, self.driver.l3_binding) self.bigip_snat_manager = BigipSnatManager( self.driver, self.l2_service, self.driver.l3_binding) self.rds_cache = {} self.interface_mapping = self.l2_service.interface_mapping self.network_helper = NetworkHelper() self.service_adapter = self.driver.service_adapter
def test_network_helper_add_fdb_entries(self, l2_service, service, bigips): # add first member fdb entry loadbalancer = None members = list() member = service['members'][0] network_id = member['network_id'] member['network'] = service['networks'][network_id] members.append(member) tunnel_records = l2_service.create_fdb_records(loadbalancer, members) network_helper = NetworkHelper() fdb_entry = mock.MagicMock() fdb_entry.modify = mock.MagicMock() bigip = bigips[0] bigip.tm.net.fdb.tunnels.tunnel.load = mock.MagicMock( return_value=fdb_entry) network_helper.add_fdb_entries(bigip, fdb_entries=tunnel_records) # expect to modify with first member's VTEP and MAC addr fdb_entry.modify.assert_called_with( records=[{ 'endpoint': '192.168.130.59', 'name': 'fa:16:3e:0d:fa:c8' }]) # add second member fdb entry members = list() member = service['members'][1] network_id = member['network_id'] member['network'] = service['networks'][network_id] members.append(member) tunnel_records = l2_service.create_fdb_records(loadbalancer, members) network_helper.add_fdb_entries(bigip, fdb_entries=tunnel_records) # expect to modify with second member's VTEP and MAC addr fdb_entry.modify.assert_called_with( records=[{ 'endpoint': '192.168.130.60', 'name': 'fa:16:3e:0d:fa:c6' }])
def network_helper(self, bigip): nh = NetworkHelper() nh.get_route_domain_by_id = mock.MagicMock( return_value=bigip.tm.net.route_domains.route_domain) return nh
class NetworkServiceBuilder(object): def __init__(self, f5_global_routed_mode, conf, driver, l3_binding=None): self.f5_global_routed_mode = f5_global_routed_mode self.conf = conf self.driver = driver self.l3_binding = l3_binding self.l2_service = L2ServiceBuilder(driver, f5_global_routed_mode) self.bigip_selfip_manager = BigipSelfIpManager(self.driver, self.l2_service, self.driver.l3_binding) self.bigip_snat_manager = BigipSnatManager(self.driver, self.l2_service, self.driver.l3_binding) self.rds_cache = {} self.interface_mapping = self.l2_service.interface_mapping self.network_helper = NetworkHelper() self.service_adapter = self.driver.service_adapter def post_init(self): # Run and Post Initialization Tasks """ # run any post initialized tasks, now that the agent # is fully connected self.l2_service.post_init() def tunnel_sync(self, tunnel_ips): self.l2_service.tunnel_sync(tunnel_ips) def set_tunnel_rpc(self, tunnel_rpc): # Provide FDB Connector with ML2 RPC access """ self.l2_service.set_tunnel_rpc(tunnel_rpc) def set_l2pop_rpc(self, l2pop_rpc): # Provide FDB Connector with ML2 RPC access """ self.l2_service.set_l2pop_rpc(l2pop_rpc) def initialize_vcmp(self): self.l2_service.initialize_vcmp_manager() def initialize_tunneling(self): # setup tunneling vtep_folder = self.conf.f5_vtep_folder vtep_selfip_name = self.conf.f5_vtep_selfip_name local_ips = [] for bigip in self.driver.get_all_bigips(): if not vtep_folder or vtep_folder.lower() == 'none': vtep_folder = 'Common' if vtep_selfip_name and \ not vtep_selfip_name.lower() == 'none': # profiles may already exist # create vxlan_multipoint_profile` self.network_helper.create_vxlan_multipoint_profile( bigip, 'vxlan_ovs', partition='Common') # create l2gre_multipoint_profile self.network_helper.create_l2gre_multipoint_profile( bigip, 'gre_ovs', partition='Common') # find the IP address for the selfip for each box local_ip = self.bigip_selfip_manager.get_selfip_addr( bigip, vtep_selfip_name, partition=vtep_folder) if local_ip: bigip.local_ip = local_ip local_ips.append(local_ip) else: raise f5_ex.MissingVTEPAddress( 'device %s missing vtep selfip %s' % (bigip.device_name, '/' + vtep_folder + '/' + vtep_selfip_name)) return local_ips def prep_service_networking(self, service, traffic_group): # Assure network connectivity is established on all bigips if self.conf.f5_global_routed_mode or not service['loadbalancer']: return if self.conf.use_namespaces: try: LOG.debug("Annotating the service definition networks " "with route domain ID.") self._annotate_service_route_domains(service) except Exception as err: LOG.exception(err) raise f5_ex.RouteDomainCreationException( "Route domain annotation error") # Per Device Network Connectivity (VLANs or Tunnels) subnetsinfo = self._get_subnets_to_assure(service) for (assure_bigip, subnetinfo) in (itertools.product(self.driver.get_all_bigips(), subnetsinfo)): LOG.debug("Assuring per device network connectivity " "for %s on subnet %s." % (assure_bigip.hostname, subnetinfo['subnet'])) # Make sure the L2 network is established self.l2_service.assure_bigip_network(assure_bigip, subnetinfo['network']) # Connect the BigIP device to network, by getting # a self-ip address on the subnet. self.bigip_selfip_manager.assure_bigip_selfip( assure_bigip, service, subnetinfo) # L3 Shared Config assure_bigips = self.driver.get_config_bigips() LOG.debug("Getting subnetinfo for ...") LOG.debug(assure_bigips) for subnetinfo in subnetsinfo: if self.conf.f5_snat_addresses_per_subnet > 0: self._assure_subnet_snats(assure_bigips, service, subnetinfo) if subnetinfo['is_for_member'] and not self.conf.f5_snat_mode: try: self._allocate_gw_addr(subnetinfo) except KeyError as err: raise f5_ex.VirtualServerCreationException(err.message) for assure_bigip in assure_bigips: # If we are not using SNATS, attempt to become # the subnet's default gateway. self.bigip_selfip_manager.assure_gateway_on_subnet( assure_bigip, subnetinfo, traffic_group) def _annotate_service_route_domains(self, service): # Add route domain notation to pool member and vip addresses. LOG.debug("Service before route domains: %s" % service) tenant_id = service['loadbalancer']['tenant_id'] self.update_rds_cache(tenant_id) if 'members' in service: for member in service['members']: if 'address' in member: LOG.debug("processing member %s" % member['address']) if 'network_id' in member and member['network_id']: member_network = ( self.service_adapter.get_network_from_service( service, member['network_id'])) member_subnet = ( self.service_adapter.get_subnet_from_service( service, member['subnet_id'])) if member_network: self.assign_route_domain(tenant_id, member_network, member_subnet) rd_id = ('%' + str(member_network['route_domain_id'])) member['address'] += rd_id else: member['address'] += '%0' if 'vip_address' in service['loadbalancer']: loadbalancer = service['loadbalancer'] if 'network_id' in loadbalancer: lb_network = self.service_adapter.get_network_from_service( service, loadbalancer['network_id']) vip_subnet = self.service_adapter.get_subnet_from_service( service, loadbalancer['vip_subnet_id']) self.assign_route_domain(tenant_id, lb_network, vip_subnet) rd_id = '%' + str(lb_network['route_domain_id']) service['loadbalancer']['vip_address'] += rd_id else: service['loadbalancer']['vip_address'] += '%0' LOG.debug("Service after route domains: %s" % service) def assign_route_domain(self, tenant_id, network, subnet): # Assign route domain for a network if self.l2_service.is_common_network(network): network['route_domain_id'] = 0 return LOG.debug("assign route domain get from cache %s" % network) route_domain_id = self.get_route_domain_from_cache(network) if route_domain_id is not None: network['route_domain_id'] = route_domain_id return LOG.debug("max namespaces: %s" % self.conf.max_namespaces_per_tenant) LOG.debug("max namespaces == 1: %s" % (self.conf.max_namespaces_per_tenant == 1)) if self.conf.max_namespaces_per_tenant == 1: bigip = self.driver.get_bigip() LOG.debug("bigip before get_domain: %s" % bigip) partition_id = self.service_adapter.get_folder_name(tenant_id) tenant_rd = self.network_helper.get_route_domain( bigip, partition=partition_id) network['route_domain_id'] = tenant_rd.id return LOG.debug("assign route domain checking for available route domain") # need new route domain ? check_cidr = netaddr.IPNetwork(subnet['cidr']) placed_route_domain_id = None for route_domain_id in self.rds_cache[tenant_id]: LOG.debug("checking rd %s" % route_domain_id) rd_entry = self.rds_cache[tenant_id][route_domain_id] overlapping_subnet = None for net_shortname in rd_entry: LOG.debug("checking net %s" % net_shortname) net_entry = rd_entry[net_shortname] for exist_subnet_id in net_entry['subnets']: if exist_subnet_id == subnet['id']: continue exist_subnet = net_entry['subnets'][exist_subnet_id] exist_cidr = exist_subnet['cidr'] if check_cidr in exist_cidr or exist_cidr in check_cidr: overlapping_subnet = exist_subnet LOG.debug( 'rd %s: overlaps with subnet %s id: %s' % ((route_domain_id, exist_subnet, exist_subnet_id))) break if overlapping_subnet: # no need to keep looking break if not overlapping_subnet: placed_route_domain_id = route_domain_id break if placed_route_domain_id is None: if (len(self.rds_cache[tenant_id]) < self.conf.max_namespaces_per_tenant): placed_route_domain_id = self._create_aux_rd(tenant_id) self.rds_cache[tenant_id][placed_route_domain_id] = {} LOG.debug("Tenant %s now has %d route domains" % (tenant_id, len(self.rds_cache[tenant_id]))) else: raise Exception("Cannot allocate route domain") LOG.debug("Placed in route domain %s" % placed_route_domain_id) rd_entry = self.rds_cache[tenant_id][placed_route_domain_id] net_short_name = self.get_neutron_net_short_name(network) if net_short_name not in rd_entry: rd_entry[net_short_name] = {'subnets': {}} net_subnets = rd_entry[net_short_name]['subnets'] net_subnets[subnet['id']] = {'cidr': check_cidr} network['route_domain_id'] = placed_route_domain_id def _create_aux_rd(self, tenant_id): # Create a new route domain route_domain_id = None for bigip in self.driver.get_all_bigips(): partition_id = self.service_adapter.get_folder_name(tenant_id) bigip_route_domain_id = self.network_helper.create_route_domain( bigip, partition=partition_id, strictness=self.conf.f5_route_domain_strictness, is_aux=True) if route_domain_id is None: route_domain_id = bigip_route_domain_id.id elif bigip_route_domain_id.id != route_domain_id: # FixME error LOG.debug( "Bigips allocated two different route domains!: %s %s" % (bigip_route_domain_id, route_domain_id)) LOG.debug("Allocated route domain %s for tenant %s" % (route_domain_id, tenant_id)) return route_domain_id # The purpose of the route domain subnet cache is to # determine whether there is an existing bigip # subnet that conflicts with a new one being # assigned to the route domain. """ # route domain subnet cache rds_cache = {'<tenant_id>': { {'0': { '<network type>-<segmentation id>': [ 'subnets': [ '<subnet id>': { 'cidr': '<cidr>' } ], '1': {}}}} """ def update_rds_cache(self, tenant_id): # Update the route domain cache from bigips if tenant_id not in self.rds_cache: LOG.debug("rds_cache: adding tenant %s" % tenant_id) self.rds_cache[tenant_id] = {} for bigip in self.driver.get_all_bigips(): self.update_rds_cache_bigip(tenant_id, bigip) LOG.debug("rds_cache updated: " + str(self.rds_cache)) def update_rds_cache_bigip(self, tenant_id, bigip): # Update the route domain cache for this tenant # with information from bigip's vlan and tunnels LOG.debug("rds_cache: processing bigip %s" % bigip.device_name) route_domain_ids = self.network_helper.get_route_domain_ids( bigip, partition=self.service_adapter.get_folder_name(tenant_id)) # LOG.debug("rds_cache: got bigip route domains: %s" % route_domains) for route_domain_id in route_domain_ids: self.update_rds_cache_bigip_rd_vlans(tenant_id, bigip, route_domain_id) def update_rds_cache_bigip_rd_vlans(self, tenant_id, bigip, route_domain_id): # Update the route domain cache with information # from the bigip vlans and tunnels from # this route domain LOG.debug("rds_cache: processing bigip %s rd %s" % (bigip.device_name, route_domain_id)) # this gets tunnels too partition_id = self.service_adapter.get_folder_name(tenant_id) rd_vlans = self.network_helper.get_vlans_in_route_domain_by_id( bigip, partition=partition_id, id=route_domain_id) LOG.debug("rds_cache: bigip %s rd %s vlans: %s" % (bigip.device_name, route_domain_id, rd_vlans)) if len(rd_vlans) == 0: LOG.debug("No vlans found for route domain: %d" % (route_domain_id)) return # make sure this rd has a cache entry tenant_entry = self.rds_cache[tenant_id] if route_domain_id not in tenant_entry: tenant_entry[route_domain_id] = {} # for every VLAN or TUNNEL on this bigip... for rd_vlan in rd_vlans: self.update_rds_cache_bigip_vlan(tenant_id, bigip, route_domain_id, rd_vlan) def update_rds_cache_bigip_vlan(self, tenant_id, bigip, route_domain_id, rd_vlan): # Update the route domain cache with information # from the bigip vlan or tunnel LOG.debug("rds_cache: processing bigip %s rd %d vlan %s" % (bigip.device_name, route_domain_id, rd_vlan)) net_short_name = self.get_bigip_net_short_name(bigip, tenant_id, rd_vlan) # make sure this net has a cache entry tenant_entry = self.rds_cache[tenant_id] rd_entry = tenant_entry[route_domain_id] if net_short_name not in rd_entry: rd_entry[net_short_name] = {'subnets': {}} net_subnets = rd_entry[net_short_name]['subnets'] partition_id = self.service_adapter.get_folder_name(tenant_id) LOG.debug("Calling get_selfips with: partition %s and vlan_name %s", partition_id, rd_vlan) selfips = self.bigip_selfip_manager.get_selfips(bigip, partition=partition_id, vlan_name=rd_vlan) LOG.debug("rds_cache: got selfips") for selfip in selfips: LOG.debug( "rds_cache: processing bigip %s rd %s vlan %s self %s" % (bigip.device_name, route_domain_id, rd_vlan, selfip.name)) if bigip.device_name not in selfip.name: LOG.error( "rds_cache: Found unexpected selfip %s for tenant %s" % (selfip.name, tenant_id)) continue subnet_id = selfip.name.split(bigip.device_name + '-')[1] # convert 10.1.1.1%1/24 to 10.1.1.1/24 (addr, netbits) = selfip.address.split('/') addr = addr.split('%')[0] selfip.address = addr + '/' + netbits # selfip addresses will have slash notation: 10.1.1.1/24 netip = netaddr.IPNetwork(selfip.address) LOG.debug("rds_cache: updating subnet %s with %s" % (subnet_id, str(netip.cidr))) net_subnets[subnet_id] = {'cidr': netip.cidr} LOG.debug("rds_cache: now %s" % self.rds_cache) def get_route_domain_from_cache(self, network): # Get route domain from cache by network net_short_name = self.get_neutron_net_short_name(network) for tenant_id in self.rds_cache: tenant_cache = self.rds_cache[tenant_id] for route_domain_id in tenant_cache: if net_short_name in tenant_cache[route_domain_id]: return route_domain_id def remove_from_rds_cache(self, network, subnet): # Get route domain from cache by network LOG.debug("remove_from_rds_cache") net_short_name = self.get_neutron_net_short_name(network) for tenant_id in self.rds_cache: LOG.debug("rds_cache: processing remove for %s" % tenant_id) deleted_rds = [] tenant_cache = self.rds_cache[tenant_id] for route_domain_id in tenant_cache: if net_short_name in tenant_cache[route_domain_id]: net_entry = tenant_cache[route_domain_id][net_short_name] if subnet['id'] in net_entry['subnets']: del net_entry['subnets'][subnet['id']] if len(net_entry['subnets']) == 0: del net_entry['subnets'] if len(tenant_cache[route_domain_id][net_short_name]) == 0: del tenant_cache[route_domain_id][net_short_name] if len(self.rds_cache[tenant_id][route_domain_id]) == 0: deleted_rds.append(route_domain_id) for rd in deleted_rds: LOG.debug("removing route domain %d from tenant %s" % (rd, tenant_id)) del self.rds_cache[tenant_id][rd] def get_bigip_net_short_name(self, bigip, tenant_id, network_name): # Return <network_type>-<seg_id> for bigip network LOG.debug("get_bigip_net_short_name: %s:%s" % (tenant_id, network_name)) partition_id = self.service_adapter.get_folder_name(tenant_id) LOG.debug("network_name %s", network_name.split('/')) network_name = network_name.split("/")[-1] if 'tunnel-gre-' in network_name: tunnel_key = self.network_helper.get_tunnel_key( bigip, network_name, partition=partition_id) return 'gre-%s' % tunnel_key elif 'tunnel-vxlan-' in network_name: LOG.debug("Getting tunnel key for VXLAN: %s", network_name) tunnel_key = self.network_helper.get_tunnel_key( bigip, network_name, partition=partition_id) return 'vxlan-%s' % tunnel_key else: LOG.debug("Getting tunnel key for VLAN: %s", network_name) vlan_id = self.network_helper.get_vlan_id(bigip, name=network_name, partition=partition_id) return 'vlan-%s' % vlan_id @staticmethod def get_neutron_net_short_name(network): # Return <network_type>-<seg_id> for neutron network net_type = network['provider:network_type'] net_seg_key = network['provider:segmentation_id'] return net_type + '-' + str(net_seg_key) def _assure_subnet_snats(self, assure_bigips, service, subnetinfo): # Ensure snat for subnet exists on bigips tenant_id = service['loadbalancer']['tenant_id'] subnet = subnetinfo['subnet'] snats_per_subnet = self.conf.f5_snat_addresses_per_subnet assure_bigips = \ [bigip for bigip in assure_bigips if tenant_id not in bigip.assured_tenant_snat_subnets or subnet['id'] not in bigip.assured_tenant_snat_subnets[tenant_id]] LOG.debug("_assure_subnet_snats: getting snat addrs for: %s" % subnet['id']) if len(assure_bigips): snat_addrs = self.bigip_snat_manager.get_snat_addrs( subnetinfo, tenant_id, snats_per_subnet) if len(snat_addrs) != snats_per_subnet: raise f5_ex.SNAT_CreationException( "Unable to satisfy request to allocate %d " "snats. Actual SNAT count: %d SNATs" % (snats_per_subnet, len(snat_addrs))) for assure_bigip in assure_bigips: self.bigip_snat_manager.assure_bigip_snats( assure_bigip, subnetinfo, snat_addrs, tenant_id) def _allocate_gw_addr(self, subnetinfo): # Create a name for the port and for the IP Forwarding # Virtual Server as well as the floating Self IP which # will answer ARP for the members need_port_for_gateway = False network = subnetinfo['network'] subnet = subnetinfo['subnet'] if not network or not subnet: LOG.error('Attempted to create default gateway' ' for network with no id...skipping.') return if not subnet['gateway_ip']: raise KeyError("attempting to create gateway on subnet without " "gateway ip address specified.") gw_name = "gw-" + subnet['id'] ports = self.driver.plugin_rpc.get_port_by_name(port_name=gw_name) if len(ports) < 1: need_port_for_gateway = True # There was no port on this agent's host, so get one from Neutron if need_port_for_gateway: try: rpc = self.driver.plugin_rpc new_port = rpc.create_port_on_subnet_with_specific_ip( subnet_id=subnet['id'], mac_address=None, name=gw_name, ip_address=subnet['gateway_ip']) LOG.info('gateway IP for subnet %s will be port %s' % (subnet['id'], new_port['id'])) except Exception as exc: ermsg = 'Invalid default gateway for subnet %s:%s - %s.' \ % (subnet['id'], subnet['gateway_ip'], exc.message) ermsg += " SNAT will not function and load balancing" ermsg += " support will likely fail. Enable f5_snat_mode." LOG.exception(ermsg) return True def post_service_networking(self, service, all_subnet_hints): # Assure networks are deleted from big-ips if self.conf.f5_global_routed_mode: return # L2toL3 networking layer # Non Shared Config - Local Per BIG-IP self.update_bigip_l2(service) # Delete shared config objects deleted_names = set() for bigip in self.driver.get_config_bigips(): LOG.debug('post_service_networking: calling ' '_assure_delete_networks del nets sh for bigip %s %s' % (bigip.device_name, all_subnet_hints)) subnet_hints = all_subnet_hints[bigip.device_name] deleted_names = deleted_names.union( self._assure_delete_nets_shared(bigip, service, subnet_hints)) # Delete non shared config objects for bigip in self.driver.get_all_bigips(): LOG.debug(' post_service_networking: calling ' ' _assure_delete_networks del nets ns for bigip %s' % bigip.device_name) subnet_hints = all_subnet_hints[bigip.device_name] deleted_names = deleted_names.union( self._assure_delete_nets_nonshared(bigip, service, subnet_hints)) for port_name in deleted_names: LOG.debug(' post_service_networking: calling ' ' del port %s' % port_name) self.driver.plugin_rpc.delete_port_by_name(port_name=port_name) def update_bigip_l2(self, service): # Update fdb entries on bigip loadbalancer = service['loadbalancer'] service_adapter = self.service_adapter for bigip in self.driver.get_all_bigips(): for member in service['members']: LOG.debug("update_bigip_l2 update service members") member['network'] = service_adapter.get_network_from_service( service, member['network_id']) member_status = member['provisioning_status'] if member_status == plugin_const.PENDING_DELETE: self.delete_bigip_member_l2(bigip, loadbalancer, member) else: self.update_bigip_member_l2(bigip, loadbalancer, member) if "network_id" not in loadbalancer: LOG.error("update_bigip_l2, expected network ID") return LOG.debug("update_bigip_l2 get network for ID %s" % loadbalancer["network_id"]) loadbalancer['network'] = service_adapter.get_network_from_service( service, loadbalancer['network_id']) lb_status = loadbalancer['provisioning_status'] if lb_status == plugin_const.PENDING_DELETE: self.delete_bigip_vip_l2(bigip, loadbalancer) else: LOG.debug("update_bigip_l2 calling update_bigip_vip_l2") self.update_bigip_vip_l2(bigip, loadbalancer) LOG.debug("update_bigip_l2 complete") def update_bigip_member_l2(self, bigip, loadbalancer, member): # update pool member l2 records network = member['network'] if network: if self.l2_service.is_common_network(network): net_folder = 'Common' else: net_folder = self.service_adapter.get_folder_name( loadbalancer['tenant_id']) fdb_info = { 'network': network, 'ip_address': member['address'], 'mac_address': member['port']['mac_address'] } self.l2_service.add_bigip_fdbs(bigip, net_folder, fdb_info, member) def delete_bigip_member_l2(self, bigip, loadbalancer, member): # Delete pool member l2 records network = member['network'] if network: if 'port' in member: if self.l2_service.is_common_network(network): net_folder = 'Common' else: net_folder = self.service_adapter.get_folder_name( loadbalancer['tenant_id']) fdb_info = { 'network': network, 'ip_address': member['address'], 'mac_address': member['port']['mac_address'] } self.l2_service.delete_bigip_fdbs(bigip, net_folder, fdb_info, member) else: LOG.error('Member on SDN has no port. Manual ' 'removal on the BIG-IP will be ' 'required. Was the vm instance ' 'deleted before the pool member ' 'was deleted?') def update_bigip_vip_l2(self, bigip, loadbalancer): # Update vip l2 records network = loadbalancer['network'] if network: if self.l2_service.is_common_network(network): net_folder = 'Common' else: net_folder = self.service_adapter.get_folder_name( loadbalancer['tenant_id']) fdb_info = { 'network': network, 'ip_address': None, 'mac_address': None } self.l2_service.add_bigip_fdbs(bigip, net_folder, fdb_info, loadbalancer) def delete_bigip_vip_l2(self, bigip, loadbalancer): # Delete loadbalancer l2 records network = loadbalancer['network'] if network: if self.l2_service.is_common_network(network): net_folder = 'Common' else: net_folder = self.service_adapter.get_folder_name( loadbalancer['tenant_id']) fdb_info = { 'network': network, 'ip_address': None, 'mac_address': None } self.l2_service.delete_bigip_fdbs(bigip, net_folder, fdb_info, loadbalancer) def _assure_delete_nets_shared(self, bigip, service, subnet_hints): # Assure shared configuration (which syncs) is deleted deleted_names = set() tenant_id = service['loadbalancer']['tenant_id'] delete_gateway = self.bigip_selfip_manager.delete_gateway_on_subnet for subnetinfo in self._get_subnets_to_delete(bigip, service, subnet_hints): try: if not self.conf.f5_snat_mode: gw_name = delete_gateway(bigip, subnetinfo) deleted_names.add(gw_name) my_deleted_names, my_in_use_subnets = \ self.bigip_snat_manager.delete_bigip_snats( bigip, subnetinfo, tenant_id) deleted_names = deleted_names.union(my_deleted_names) for in_use_subnetid in my_in_use_subnets: subnet_hints['check_for_delete_subnets'].pop( in_use_subnetid, None) except NeutronException as exc: LOG.error("assure_delete_nets_shared: exception: %s" % str(exc.msg)) except Exception as exc: LOG.error("assure_delete_nets_shared: exception: %s" % str(exc.message)) return deleted_names def _assure_delete_nets_nonshared(self, bigip, service, subnet_hints): # Delete non shared base objects for networks deleted_names = set() for subnetinfo in self._get_subnets_to_delete(bigip, service, subnet_hints): try: network = subnetinfo['network'] if self.l2_service.is_common_network(network): network_folder = 'Common' else: network_folder = self.service_adapter.get_folder_name( service['loadbalancer']['tenant_id']) subnet = subnetinfo['subnet'] if self.conf.f5_populate_static_arp: self.network_helper.arp_delete_by_subnet( bigip, subnet=subnet['cidr'], mask=None, partition=network_folder) local_selfip_name = "local-" + bigip.device_name + \ "-" + subnet['id'] selfip_address = self.bigip_selfip_manager.get_selfip_addr( bigip, local_selfip_name, partition=network_folder) if not selfip_address: LOG.error("Failed to get self IP address %s in cleanup.", local_selfip_name) self.bigip_selfip_manager.delete_selfip( bigip, local_selfip_name, partition=network_folder) if self.l3_binding and selfip_address: self.l3_binding.unbind_address(subnet_id=subnet['id'], ip_address=selfip_address) deleted_names.add(local_selfip_name) self.l2_service.delete_bigip_network(bigip, network) if subnet['id'] not in subnet_hints['do_not_delete_subnets']: subnet_hints['do_not_delete_subnets'].append(subnet['id']) self.remove_from_rds_cache(network, subnet) tenant_id = service['loadbalancer']['tenant_id'] if tenant_id in bigip.assured_tenant_snat_subnets: tenant_snat_subnets = \ bigip.assured_tenant_snat_subnets[tenant_id] if subnet['id'] in tenant_snat_subnets: tenant_snat_subnets.remove(subnet['id']) except NeutronException as exc: LOG.error("assure_delete_nets_nonshared: exception: %s" % str(exc.msg)) except Exception as exc: LOG.error("assure_delete_nets_nonshared: exception: %s" % str(exc.message)) return deleted_names def _get_subnets_to_delete(self, bigip, service, subnet_hints): # Clean up any Self IP, SNATs, networks, and folder for # services items that we deleted. subnets_to_delete = [] for subnetinfo in subnet_hints['check_for_delete_subnets'].values(): subnet = self.service_adapter.get_subnet_from_service( service, subnetinfo['subnet_id']) subnetinfo['subnet'] = subnet network = self.service_adapter.get_network_from_service( service, subnetinfo['network_id']) subnetinfo['network'] = network route_domain = network['route_domain_id'] if not subnet: continue if not self._ips_exist_on_subnet(bigip, service, subnet, route_domain): subnets_to_delete.append(subnetinfo) return subnets_to_delete def _ips_exist_on_subnet(self, bigip, service, subnet, route_domain): # Does the big-ip have any IP addresses on this subnet? LOG.debug("_ips_exist_on_subnet entry %s rd %s" % (str(subnet['cidr']), route_domain)) route_domain = str(route_domain) ipsubnet = netaddr.IPNetwork(subnet['cidr']) # Are there any virtual addresses on this subnet? folder = self.service_adapter.get_folder_name( service['loadbalancer']['tenant_id']) virtual_services = self.network_helper.get_virtual_service_insertion( bigip, partition=folder) for virt_serv in virtual_services: (_, dest) = virt_serv.items()[0] LOG.debug(" _ips_exist_on_subnet: checking vip %s" % str(dest['address'])) if len(dest['address'].split('%')) > 1: vip_route_domain = dest['address'].split('%')[1] else: vip_route_domain = '0' if vip_route_domain != route_domain: continue vip_addr = strip_domain_address(dest['address']) if netaddr.IPAddress(vip_addr) in ipsubnet: LOG.debug(" _ips_exist_on_subnet: found") return True # If there aren't any virtual addresses, are there # node addresses on this subnet? nodes = self.network_helper.get_node_addresses(bigip, partition=folder) for node in nodes: LOG.debug(" _ips_exist_on_subnet: checking node %s" % str(node)) if len(node.split('%')) > 1: node_route_domain = node.split('%')[1] else: node_route_domain = '0' if node_route_domain != route_domain: continue node_addr = strip_domain_address(node) if netaddr.IPAddress(node_addr) in ipsubnet: LOG.debug(" _ips_exist_on_subnet: found") return True LOG.debug(" _ips_exist_on_subnet exit %s" % str(subnet['cidr'])) # nothing found return False def add_bigip_fdb(self, bigip, fdb): self.l2_service.add_bigip_fdb(bigip, fdb) def remove_bigip_fdb(self, bigip, fdb): self.l2_service.remove_bigip_fdb(bigip, fdb) def update_bigip_fdb(self, bigip, fdb): self.l2_service.update_bigip_fdb(bigip, fdb) def set_context(self, context): self.l2_service.set_context(context) def vlan_exists(self, network, folder='Common'): return False def _get_subnets_to_assure(self, service): # Examine service and return active networks networks = dict() loadbalancer = service['loadbalancer'] service_adapter = self.service_adapter lb_status = loadbalancer['provisioning_status'] if lb_status != plugin_const.PENDING_DELETE: if 'network_id' in loadbalancer: network = service_adapter.get_network_from_service( service, loadbalancer['network_id']) subnet = service_adapter.get_subnet_from_service( service, loadbalancer['vip_subnet_id']) networks[network['id']] = { 'network': network, 'subnet': subnet, 'is_for_member': False } for member in service['members']: if member['provisioning_status'] != plugin_const.PENDING_DELETE: if 'network_id' in member: network = service_adapter.get_network_from_service( service, member['network_id']) subnet = service_adapter.get_subnet_from_service( service, member['subnet_id']) networks[network['id']] = { 'network': network, 'subnet': subnet, 'is_for_member': True } return networks.values()
class BigipSnatManager(object): def __init__(self, driver, l2_service, l3_binding): self.driver = driver self.l2_service = l2_service self.l3_binding = l3_binding self.snatpool_manager = BigIPResourceHelper(ResourceType.snatpool) self.snat_translation_manager = BigIPResourceHelper( ResourceType.snat_translation) self.network_helper = NetworkHelper() def _get_snat_name(self, subnet, tenant_id): # Get the snat name based on HA type if self.driver.conf.f5_ha_type == 'standalone': return 'snat-traffic-group-local-only-' + subnet['id'] elif self.driver.conf.f5_ha_type == 'pair': # REVISIT(RJB): should this name have a hyphen before subnetid return 'snat-traffic-group-1' + subnet['id'] elif self.driver.conf.f5_ha_type == 'scalen': traffic_group = self.driver.tenant_to_traffic_group(tenant_id) base_traffic_group = os.path.basename(traffic_group) return 'snat-' + base_traffic_group + '-' + subnet['id'] LOG.error('Invalid f5_ha_type:%s' % self.driver.conf.f5_ha_type) return '' def _get_snat_traffic_group(self, tenant_id): # Get the snat name based on HA type """ if self.driver.conf.f5_ha_type == 'standalone': return 'traffic-group-local-only' elif self.driver.conf.f5_ha_type == 'pair': return 'traffic-group-1' elif self.driver.conf.f5_ha_type == 'scalen': traffic_group = self.driver.tenant_to_traffic_group(tenant_id) return os.path.basename(traffic_group) # If this is an error shouldn't we raise? LOG.error('Invalid f5_ha_type:%s' % self.driver.conf.f5_ha_type) return '' def get_snat_addrs(self, subnetinfo, tenant_id): # Get the ip addresses for snat """ subnet = subnetinfo['subnet'] snat_addrs = [] snat_name = self._get_snat_name(subnet, tenant_id) for i in range(self.driver.conf.f5_snat_addresses_per_subnet): index_snat_name = snat_name + "_" + str(i) ports = self.driver.plugin_rpc.get_port_by_name( port_name=index_snat_name) if len(ports) > 0: first_port = ports[0] first_fixed_ip = first_port['fixed_ips'][0] ip_address = first_fixed_ip['ip_address'] else: new_port = self.driver.plugin_rpc.create_port_on_subnet( subnet_id=subnet['id'], mac_address=None, name=index_snat_name, fixed_address_count=1) ip_address = new_port['fixed_ips'][0]['ip_address'] snat_addrs.append(ip_address) return snat_addrs def assure_bigip_snats(self, bigip, subnetinfo, snat_addrs, tenant_id): # Ensure Snat Addresses are configured on a bigip. # Called for every bigip only in replication mode. # otherwise called once and synced. network = subnetinfo['network'] snat_info = {} if self.l2_service.is_common_network(network): snat_info['network_folder'] = 'Common' else: snat_info['network_folder'] = ( self.driver.service_adapter.get_folder_name(tenant_id) ) snat_info['pool_name'] = self.driver.service_adapter.get_folder_name( tenant_id ) snat_info['pool_folder'] = self.driver.service_adapter.get_folder_name( tenant_id ) snat_info['addrs'] = snat_addrs self._assure_bigip_snats(bigip, subnetinfo, snat_info, tenant_id) def _assure_bigip_snats(self, bigip, subnetinfo, snat_info, tenant_id): # Configure the ip addresses for snat network = subnetinfo['network'] subnet = subnetinfo['subnet'] LOG.debug("_assure_bigip_snats") if tenant_id not in bigip.assured_tenant_snat_subnets: bigip.assured_tenant_snat_subnets[tenant_id] = [] if subnet['id'] in bigip.assured_tenant_snat_subnets[tenant_id]: return snat_name = self._get_snat_name(subnet, tenant_id) for i in range(self.driver.conf.f5_snat_addresses_per_subnet): ip_address = snat_info['addrs'][i] + \ '%' + str(network['route_domain_id']) index_snat_name = snat_name + "_" + str(i) if self.l2_service.is_common_network(network): index_snat_name = '/Common/' + index_snat_name snat_traffic_group = self._get_snat_traffic_group(tenant_id) # snat.create() did the following in LBaaSv1 # Creates the SNAT # * if the traffic_group is empty it uses a const # but this seems like it should be an error see message # in this file about this # Create a SNAT Pool if a name was passed in # * Add the snat to the list of members model = { "name": index_snat_name, "partition": snat_info['network_folder'], "address": ip_address, "trafficGroup": snat_traffic_group } if not self.snat_translation_manager.exists( bigip, name=index_snat_name, partition=snat_info['network_folder']): LOG.debug("Calling SNAT translation manager CREATE.") self.snat_translation_manager.create(bigip, model) else: LOG.debug("SNAT translation_manager LOAD") self.snat_translation_manager.load( bigip, name=index_snat_name, partition=snat_info['network_folder']) model = { "name": index_snat_name, "partition": snat_info['pool_folder'], } model["members"] = ['/' + model["partition"] + '/' + index_snat_name] try: LOG.debug("Calling SNAT pool create: %s" % model) self.snatpool_manager.create(bigip, model) LOG.debug("SNAT pool created: %s" % model) except HTTPError as err: LOG.error("Create SNAT pool failed %s" % err.message) if err.response.status_code == 409: try: snatpool = self.snatpool_manager.load( bigip, name=model["name"], partition=model["partition"] ) snatpool.members.append(model["members"]) snatpool.update() except HTTPError as err: LOG.error("Update SNAT pool failed %s" % err.message) if self.l3_binding: self.l3_binding.bind_address(subnet_id=subnet['id'], ip_address=ip_address) bigip.assured_tenant_snat_subnets[tenant_id].append(subnet['id']) def delete_bigip_snats(self, bigip, subnetinfo, tenant_id): # Assure shared snat configuration (which syncs) is deleted. # if not subnetinfo['network']: LOG.error('Attempted to delete selfip and snats ' 'for missing network ... skipping.') return set() return self._delete_bigip_snats(bigip, subnetinfo, tenant_id) def _remove_assured_tenant_snat_subnet(self, bigip, tenant_id, subnet): # Remove ref for the subnet for this tenant""" if tenant_id in bigip.assured_tenant_snat_subnets: tenant_snat_subnets = \ bigip.assured_tenant_snat_subnets[tenant_id] if tenant_snat_subnets and subnet['id'] in tenant_snat_subnets: LOG.debug( 'Remove subnet id %s from ' 'bigip.assured_tenant_snat_subnets for tenant %s' % (subnet['id'], tenant_id)) tenant_snat_subnets.remove(subnet['id']) else: LOG.debug( 'Subnet id %s does not exist in ' 'bigip.assured_tenant_snat_subnets for tenant %s' % (subnet['id'], tenant_id)) else: LOG.debug( 'Tenant id %s does not exist in ' 'bigip.assured_tenant_snat_subnets' % tenant_id) def _delete_bigip_snats(self, bigip, subnetinfo, tenant_id): # Assure snats deleted in standalone mode """ network = subnetinfo['network'] subnet = subnetinfo['subnet'] partition = self.driver.service_adapter.get_folder_name(tenant_id) deleted_names = set() in_use_subnets = set() # Delete SNATs on traffic-group-local-only snat_name = self._get_snat_name(subnet, tenant_id) for i in range(self.driver.conf.f5_snat_addresses_per_subnet): index_snat_name = snat_name + "_" + str(i) if self.l2_service.is_common_network(network): tmos_snat_name = '/Common/' + index_snat_name else: tmos_snat_name = index_snat_name if self.l3_binding: try: snat_xlate = self.snat_translation_manager.load( bigip, name=index_snat_name, partition=partition) self.l3_binding.unbind_address( subnet_id=subnet['id'], ip_address=snat_xlate.address) except HTTPError as err: LOG.error("Load SNAT xlate failed %s" % err.message) # Remove translation address from tenant snat pool # This seems strange that name and partition are tenant_id # but that is what the v1 code was doing. # The v1 code was also comparing basename in some cases # which seems dangerous because the folder may be in play? # # Revised (jl): It appears that v2 SNATs are created with a # name, not tenant_id, so we need to load SNAT by name. LOG.debug('Remove translation address from tenant SNAT pool') try: snatpool = self.snatpool_manager.load(bigip, index_snat_name, partition) snatpool.members = [ member for member in snatpool.members if os.path.basename(member) != tmos_snat_name ] # Delete snat pool if empty (no members) # In LBaaSv1 the snat.remove_from_pool() method did this if # there was only one member and it matched the one we were # deleting making this call basically useless, but the # code above makes this still necessary and probably what the # original authors intended anyway since there is logging here # but not in the snat.py module from LBaaSv1 LOG.debug('Check if snat pool is empty') if not snatpool.members: LOG.debug('Snat pool is empty - delete snatpool') try: snatpool.delete() except HTTPError as err: LOG.error("Delete SNAT pool failed %s" % err.message) else: LOG.debug('Snat pool is not empty - update snatpool') try: snatpool.update() except HTTPError as err: LOG.error("Update SNAT pool failed %s" % err.message) except HTTPError as err: LOG.error("Failed to load SNAT pool %s" % err.message) # Check if subnet in use by any tenants/snatpools. If in use, # add subnet to hints list of subnets in use. self._remove_assured_tenant_snat_subnet(bigip, tenant_id, subnet) LOG.debug( 'Check cache for subnet %s in use by other tenant' % subnet['id']) in_use_count = 0 for loop_tenant_id in bigip.assured_tenant_snat_subnets: tenant_snat_subnets = \ bigip.assured_tenant_snat_subnets[loop_tenant_id] if subnet['id'] in tenant_snat_subnets: LOG.debug( 'Subnet %s in use (tenant %s)' % (subnet['id'], loop_tenant_id)) in_use_count += 1 if in_use_count: in_use_subnets.add(subnet['id']) else: LOG.debug('Check subnet in use by any tenant') member_use_count = \ self.network_helper.get_snatpool_member_use_count( bigip, subnet['id']) if member_use_count: LOG.debug('Subnet in use - do not delete') in_use_subnets.add(subnet['id']) else: LOG.debug('Subnet not in use - delete') # Check if trans addr in use by any snatpool. If not in use, # okay to delete associated neutron port. LOG.debug('Check trans addr %s in use.' % tmos_snat_name) in_use_count = \ self.network_helper.get_snatpool_member_use_count( bigip, tmos_snat_name) if not in_use_count: LOG.debug('Trans addr not in use - delete') deleted_names.add(index_snat_name) else: LOG.debug('Trans addr in use - do not delete') return deleted_names, in_use_subnets
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 NetworkServiceBuilder(object): def __init__(self, f5_global_routed_mode, conf, driver, l3_binding=None): self.f5_global_routed_mode = f5_global_routed_mode self.conf = conf self.driver = driver self.l3_binding = l3_binding self.l2_service = L2ServiceBuilder(driver, f5_global_routed_mode) self.bigip_selfip_manager = BigipSelfIpManager( self.driver, self.l2_service, self.driver.l3_binding) self.bigip_snat_manager = BigipSnatManager( self.driver, self.l2_service, self.driver.l3_binding) self.vlan_manager = resource_helper.BigIPResourceHelper( resource_helper.ResourceType.vlan) self.rds_cache = {} self.interface_mapping = self.l2_service.interface_mapping self.network_helper = NetworkHelper() self.service_adapter = self.driver.service_adapter def post_init(self): # Run and Post Initialization Tasks """ # run any post initialized tasks, now that the agent # is fully connected self.l2_service.post_init() def tunnel_sync(self, tunnel_ips): self.l2_service.tunnel_sync(tunnel_ips) def set_tunnel_rpc(self, tunnel_rpc): # Provide FDB Connector with ML2 RPC access """ self.l2_service.set_tunnel_rpc(tunnel_rpc) def set_l2pop_rpc(self, l2pop_rpc): # Provide FDB Connector with ML2 RPC access """ self.l2_service.set_l2pop_rpc(l2pop_rpc) def initialize_vcmp(self): self.l2_service.initialize_vcmp_manager() def initialize_tunneling(self): # setup tunneling vtep_folder = self.conf.f5_vtep_folder vtep_selfip_name = self.conf.f5_vtep_selfip_name local_ips = [] for bigip in self.driver.get_all_bigips(): bigip.local_ip = None if not vtep_folder or vtep_folder.lower() == 'none': vtep_folder = 'Common' if vtep_selfip_name and \ not vtep_selfip_name.lower() == 'none': # profiles may already exist # create vxlan_multipoint_profile` self.network_helper.create_vxlan_multipoint_profile( bigip, 'vxlan_ovs', partition='Common') # create l2gre_multipoint_profile self.network_helper.create_l2gre_multipoint_profile( bigip, 'gre_ovs', partition='Common') # find the IP address for the selfip for each box local_ip = self.bigip_selfip_manager.get_selfip_addr( bigip, vtep_selfip_name, partition=vtep_folder ) if local_ip: bigip.local_ip = local_ip local_ips.append(local_ip) else: raise f5_ex.MissingVTEPAddress( 'device %s missing vtep selfip %s' % (bigip.device_name, '/' + vtep_folder + '/' + vtep_selfip_name)) return local_ips def prep_service_networking(self, service, traffic_group): # Assure network connectivity is established on all bigips if self.conf.f5_global_routed_mode or not service['loadbalancer']: return if self.conf.use_namespaces: try: LOG.debug("Annotating the service definition networks " "with route domain ID.") self._annotate_service_route_domains(service) except Exception as err: LOG.exception(err) raise f5_ex.RouteDomainCreationException( "Route domain annotation error") # Per Device Network Connectivity (VLANs or Tunnels) subnetsinfo = self._get_subnets_to_assure(service) for (assure_bigip, subnetinfo) in ( itertools.product(self.driver.get_all_bigips(), subnetsinfo)): LOG.debug("Assuring per device network connectivity " "for %s on subnet %s." % (assure_bigip.hostname, subnetinfo['subnet'])) # Make sure the L2 network is established self.l2_service.assure_bigip_network( assure_bigip, subnetinfo['network']) # Connect the BigIP device to network, by getting # a self-ip address on the subnet. self.bigip_selfip_manager.assure_bigip_selfip( assure_bigip, service, subnetinfo) # L3 Shared Config assure_bigips = self.driver.get_config_bigips() LOG.debug("Getting subnetinfo for ...") LOG.debug(assure_bigips) for subnetinfo in subnetsinfo: if self.conf.f5_snat_addresses_per_subnet > 0: self._assure_subnet_snats(assure_bigips, service, subnetinfo) if subnetinfo['is_for_member'] and not self.conf.f5_snat_mode: try: self._allocate_gw_addr(subnetinfo) except KeyError as err: raise f5_ex.VirtualServerCreationException(err.message) for assure_bigip in assure_bigips: # If we are not using SNATS, attempt to become # the subnet's default gateway. self.bigip_selfip_manager.assure_gateway_on_subnet( assure_bigip, subnetinfo, traffic_group) def _annotate_service_route_domains(self, service): # Add route domain notation to pool member and vip addresses. LOG.debug("Service before route domains: %s" % service) tenant_id = service['loadbalancer']['tenant_id'] self.update_rds_cache(tenant_id) if 'members' in service: for member in service['members']: if 'address' in member: LOG.debug("processing member %s" % member['address']) if 'network_id' in member and member['network_id']: member_network = ( self.service_adapter.get_network_from_service( service, member['network_id'] )) member_subnet = ( self.service_adapter.get_subnet_from_service( service, member['subnet_id'] )) if member_network: self.assign_route_domain( tenant_id, member_network, member_subnet) rd_id = ( '%' + str(member_network['route_domain_id']) ) member['address'] += rd_id else: member['address'] += '%0' if 'vip_address' in service['loadbalancer']: loadbalancer = service['loadbalancer'] if 'network_id' in loadbalancer: lb_network = self.service_adapter.get_network_from_service( service, loadbalancer['network_id']) vip_subnet = self.service_adapter.get_subnet_from_service( service, loadbalancer['vip_subnet_id']) self.assign_route_domain( tenant_id, lb_network, vip_subnet) rd_id = '%' + str(lb_network['route_domain_id']) service['loadbalancer']['vip_address'] += rd_id else: service['loadbalancer']['vip_address'] += '%0' LOG.debug("Service after route domains: %s" % service) def assign_route_domain(self, tenant_id, network, subnet): # Assign route domain for a network if self.l2_service.is_common_network(network): network['route_domain_id'] = 0 return LOG.debug("assign route domain get from cache %s" % network) route_domain_id = self.get_route_domain_from_cache(network) if route_domain_id is not None: network['route_domain_id'] = route_domain_id return LOG.debug("max namespaces: %s" % self.conf.max_namespaces_per_tenant) LOG.debug("max namespaces == 1: %s" % (self.conf.max_namespaces_per_tenant == 1)) if self.conf.max_namespaces_per_tenant == 1: bigip = self.driver.get_bigip() LOG.debug("bigip before get_domain: %s" % bigip) partition_id = self.service_adapter.get_folder_name( tenant_id) tenant_rd = self.network_helper.get_route_domain( bigip, partition=partition_id) network['route_domain_id'] = tenant_rd.id return LOG.debug("assign route domain checking for available route domain") # need new route domain ? check_cidr = netaddr.IPNetwork(subnet['cidr']) placed_route_domain_id = None for route_domain_id in self.rds_cache[tenant_id]: LOG.debug("checking rd %s" % route_domain_id) rd_entry = self.rds_cache[tenant_id][route_domain_id] overlapping_subnet = None for net_shortname in rd_entry: LOG.debug("checking net %s" % net_shortname) net_entry = rd_entry[net_shortname] for exist_subnet_id in net_entry['subnets']: if exist_subnet_id == subnet['id']: continue exist_subnet = net_entry['subnets'][exist_subnet_id] exist_cidr = exist_subnet['cidr'] if check_cidr in exist_cidr or exist_cidr in check_cidr: overlapping_subnet = exist_subnet LOG.debug('rd %s: overlaps with subnet %s id: %s' % ( (route_domain_id, exist_subnet, exist_subnet_id))) break if overlapping_subnet: # no need to keep looking break if not overlapping_subnet: placed_route_domain_id = route_domain_id break if placed_route_domain_id is None: if (len(self.rds_cache[tenant_id]) < self.conf.max_namespaces_per_tenant): placed_route_domain_id = self._create_aux_rd(tenant_id) self.rds_cache[tenant_id][placed_route_domain_id] = {} LOG.debug("Tenant %s now has %d route domains" % (tenant_id, len(self.rds_cache[tenant_id]))) else: raise Exception("Cannot allocate route domain") LOG.debug("Placed in route domain %s" % placed_route_domain_id) rd_entry = self.rds_cache[tenant_id][placed_route_domain_id] net_short_name = self.get_neutron_net_short_name(network) if net_short_name not in rd_entry: rd_entry[net_short_name] = {'subnets': {}} net_subnets = rd_entry[net_short_name]['subnets'] net_subnets[subnet['id']] = {'cidr': check_cidr} network['route_domain_id'] = placed_route_domain_id def _create_aux_rd(self, tenant_id): # Create a new route domain route_domain_id = None for bigip in self.driver.get_all_bigips(): partition_id = self.service_adapter.get_folder_name(tenant_id) bigip_route_domain_id = self.network_helper.create_route_domain( bigip, partition=partition_id, strictness=self.conf.f5_route_domain_strictness, is_aux=True) if route_domain_id is None: route_domain_id = bigip_route_domain_id.id elif bigip_route_domain_id.id != route_domain_id: # FixME error LOG.debug( "Bigips allocated two different route domains!: %s %s" % (bigip_route_domain_id, route_domain_id)) LOG.debug("Allocated route domain %s for tenant %s" % (route_domain_id, tenant_id)) return route_domain_id # The purpose of the route domain subnet cache is to # determine whether there is an existing bigip # subnet that conflicts with a new one being # assigned to the route domain. """ # route domain subnet cache rds_cache = {'<tenant_id>': { {'0': { '<network type>-<segmentation id>': [ 'subnets': [ '<subnet id>': { 'cidr': '<cidr>' } ], '1': {}}}} """ def update_rds_cache(self, tenant_id): # Update the route domain cache from bigips if tenant_id not in self.rds_cache: LOG.debug("rds_cache: adding tenant %s" % tenant_id) self.rds_cache[tenant_id] = {} for bigip in self.driver.get_all_bigips(): self.update_rds_cache_bigip(tenant_id, bigip) LOG.debug("rds_cache updated: " + str(self.rds_cache)) def update_rds_cache_bigip(self, tenant_id, bigip): # Update the route domain cache for this tenant # with information from bigip's vlan and tunnels LOG.debug("rds_cache: processing bigip %s" % bigip.device_name) route_domain_ids = self.network_helper.get_route_domain_ids( bigip, partition=self.service_adapter.get_folder_name(tenant_id)) # LOG.debug("rds_cache: got bigip route domains: %s" % route_domains) for route_domain_id in route_domain_ids: self.update_rds_cache_bigip_rd_vlans( tenant_id, bigip, route_domain_id) def update_rds_cache_bigip_rd_vlans( self, tenant_id, bigip, route_domain_id): # Update the route domain cache with information # from the bigip vlans and tunnels from # this route domain LOG.debug("rds_cache: processing bigip %s rd %s" % (bigip.device_name, route_domain_id)) # this gets tunnels too partition_id = self.service_adapter.get_folder_name(tenant_id) rd_vlans = self.network_helper.get_vlans_in_route_domain_by_id( bigip, partition=partition_id, id=route_domain_id ) LOG.debug("rds_cache: bigip %s rd %s vlans: %s" % (bigip.device_name, route_domain_id, rd_vlans)) if len(rd_vlans) == 0: LOG.debug("No vlans found for route domain: %d" % (route_domain_id)) return # make sure this rd has a cache entry tenant_entry = self.rds_cache[tenant_id] if route_domain_id not in tenant_entry: tenant_entry[route_domain_id] = {} # for every VLAN or TUNNEL on this bigip... for rd_vlan in rd_vlans: self.update_rds_cache_bigip_vlan( tenant_id, bigip, route_domain_id, rd_vlan) def update_rds_cache_bigip_vlan( self, tenant_id, bigip, route_domain_id, rd_vlan): # Update the route domain cache with information # from the bigip vlan or tunnel LOG.debug("rds_cache: processing bigip %s rd %d vlan %s" % (bigip.device_name, route_domain_id, rd_vlan)) net_short_name = self.get_bigip_net_short_name( bigip, tenant_id, rd_vlan) # make sure this net has a cache entry tenant_entry = self.rds_cache[tenant_id] rd_entry = tenant_entry[route_domain_id] if net_short_name not in rd_entry: rd_entry[net_short_name] = {'subnets': {}} net_subnets = rd_entry[net_short_name]['subnets'] partition_id = self.service_adapter.get_folder_name(tenant_id) LOG.debug("Calling get_selfips with: partition %s and vlan_name %s", partition_id, rd_vlan) selfips = self.bigip_selfip_manager.get_selfips( bigip, partition=partition_id, vlan_name=rd_vlan ) LOG.debug("rds_cache: got selfips") for selfip in selfips: LOG.debug("rds_cache: processing bigip %s rd %s vlan %s self %s" % (bigip.device_name, route_domain_id, rd_vlan, selfip.name)) if bigip.device_name not in selfip.name: LOG.error("rds_cache: Found unexpected selfip %s for tenant %s" % (selfip.name, tenant_id)) continue subnet_id = selfip.name.split(bigip.device_name + '-')[1] # convert 10.1.1.1%1/24 to 10.1.1.1/24 (addr, netbits) = selfip.address.split('/') addr = addr.split('%')[0] selfip.address = addr + '/' + netbits # selfip addresses will have slash notation: 10.1.1.1/24 netip = netaddr.IPNetwork(selfip.address) LOG.debug("rds_cache: updating subnet %s with %s" % (subnet_id, str(netip.cidr))) net_subnets[subnet_id] = {'cidr': netip.cidr} LOG.debug("rds_cache: now %s" % self.rds_cache) def get_route_domain_from_cache(self, network): # Get route domain from cache by network net_short_name = self.get_neutron_net_short_name(network) for tenant_id in self.rds_cache: tenant_cache = self.rds_cache[tenant_id] for route_domain_id in tenant_cache: if net_short_name in tenant_cache[route_domain_id]: return route_domain_id def remove_from_rds_cache(self, network, subnet): # Get route domain from cache by network LOG.debug("remove_from_rds_cache") net_short_name = self.get_neutron_net_short_name(network) for tenant_id in self.rds_cache: LOG.debug("rds_cache: processing remove for %s" % tenant_id) deleted_rds = [] tenant_cache = self.rds_cache[tenant_id] for route_domain_id in tenant_cache: if net_short_name in tenant_cache[route_domain_id]: net_entry = tenant_cache[route_domain_id][net_short_name] if subnet['id'] in net_entry['subnets']: del net_entry['subnets'][subnet['id']] if len(net_entry['subnets']) == 0: del net_entry['subnets'] if len(tenant_cache[route_domain_id][net_short_name]) == 0: del tenant_cache[route_domain_id][net_short_name] if len(self.rds_cache[tenant_id][route_domain_id]) == 0: deleted_rds.append(route_domain_id) for rd in deleted_rds: LOG.debug("removing route domain %d from tenant %s" % (rd, tenant_id)) del self.rds_cache[tenant_id][rd] def get_bigip_net_short_name(self, bigip, tenant_id, network_name): # Return <network_type>-<seg_id> for bigip network LOG.debug("get_bigip_net_short_name: %s:%s" % ( tenant_id, network_name)) partition_id = self.service_adapter.get_folder_name(tenant_id) LOG.debug("network_name %s", network_name.split('/')) network_name = network_name.split("/")[-1] if 'tunnel-gre-' in network_name: tunnel_key = self.network_helper.get_tunnel_key( bigip, network_name, partition=partition_id ) return 'gre-%s' % tunnel_key elif 'tunnel-vxlan-' in network_name: LOG.debug("Getting tunnel key for VXLAN: %s", network_name) tunnel_key = self.network_helper.get_tunnel_key( bigip, network_name, partition=partition_id ) return 'vxlan-%s' % tunnel_key else: LOG.debug("Getting tunnel key for VLAN: %s", network_name) vlan_id = self.network_helper.get_vlan_id(bigip, name=network_name, partition=partition_id) return 'vlan-%s' % vlan_id @staticmethod def get_neutron_net_short_name(network): # Return <network_type>-<seg_id> for neutron network net_type = network['provider:network_type'] net_seg_key = network['provider:segmentation_id'] return net_type + '-' + str(net_seg_key) def _assure_subnet_snats(self, assure_bigips, service, subnetinfo): # Ensure snat for subnet exists on bigips tenant_id = service['loadbalancer']['tenant_id'] subnet = subnetinfo['subnet'] snats_per_subnet = self.conf.f5_snat_addresses_per_subnet assure_bigips = \ [bigip for bigip in assure_bigips if tenant_id not in bigip.assured_tenant_snat_subnets or subnet['id'] not in bigip.assured_tenant_snat_subnets[tenant_id]] LOG.debug("_assure_subnet_snats: getting snat addrs for: %s" % subnet['id']) if len(assure_bigips): snat_addrs = self.bigip_snat_manager.get_snat_addrs( subnetinfo, tenant_id, snats_per_subnet) if len(snat_addrs) != snats_per_subnet: raise f5_ex.SNAT_CreationException( "Unable to satisfy request to allocate %d " "snats. Actual SNAT count: %d SNATs" % (snats_per_subnet, len(snat_addrs))) for assure_bigip in assure_bigips: self.bigip_snat_manager.assure_bigip_snats( assure_bigip, subnetinfo, snat_addrs, tenant_id) def _allocate_gw_addr(self, subnetinfo): # Create a name for the port and for the IP Forwarding # Virtual Server as well as the floating Self IP which # will answer ARP for the members need_port_for_gateway = False network = subnetinfo['network'] subnet = subnetinfo['subnet'] if not network or not subnet: LOG.error('Attempted to create default gateway' ' for network with no id...skipping.') return if not subnet['gateway_ip']: raise KeyError("attempting to create gateway on subnet without " "gateway ip address specified.") gw_name = "gw-" + subnet['id'] ports = self.driver.plugin_rpc.get_port_by_name(port_name=gw_name) if len(ports) < 1: need_port_for_gateway = True # There was no port on this agent's host, so get one from Neutron if need_port_for_gateway: try: rpc = self.driver.plugin_rpc new_port = rpc.create_port_on_subnet_with_specific_ip( subnet_id=subnet['id'], mac_address=None, name=gw_name, ip_address=subnet['gateway_ip']) LOG.info('gateway IP for subnet %s will be port %s' % (subnet['id'], new_port['id'])) except Exception as exc: ermsg = 'Invalid default gateway for subnet %s:%s - %s.' \ % (subnet['id'], subnet['gateway_ip'], exc.message) ermsg += " SNAT will not function and load balancing" ermsg += " support will likely fail. Enable f5_snat_mode." LOG.exception(ermsg) return True def post_service_networking(self, service, all_subnet_hints): # Assure networks are deleted from big-ips if self.conf.f5_global_routed_mode: return # L2toL3 networking layer # Non Shared Config - Local Per BIG-IP self.update_bigip_l2(service) # Delete shared config objects deleted_names = set() for bigip in self.driver.get_config_bigips(): LOG.debug('post_service_networking: calling ' '_assure_delete_networks del nets sh for bigip %s %s' % (bigip.device_name, all_subnet_hints)) subnet_hints = all_subnet_hints[bigip.device_name] deleted_names = deleted_names.union( self._assure_delete_nets_shared(bigip, service, subnet_hints)) # Delete non shared config objects for bigip in self.driver.get_all_bigips(): LOG.debug(' post_service_networking: calling ' ' _assure_delete_networks del nets ns for bigip %s' % bigip.device_name) subnet_hints = all_subnet_hints[bigip.device_name] deleted_names = deleted_names.union( self._assure_delete_nets_nonshared( bigip, service, subnet_hints) ) for port_name in deleted_names: LOG.debug(' post_service_networking: calling ' ' del port %s' % port_name) self.driver.plugin_rpc.delete_port_by_name( port_name=port_name) def update_bigip_l2(self, service): # Update fdb entries on bigip loadbalancer = service['loadbalancer'] service_adapter = self.service_adapter for bigip in self.driver.get_all_bigips(): for member in service['members']: LOG.debug("update_bigip_l2 update service members") member['network'] = service_adapter.get_network_from_service( service, member['network_id'] ) member_status = member['provisioning_status'] if member_status == plugin_const.PENDING_DELETE: self.delete_bigip_member_l2(bigip, loadbalancer, member) else: self.update_bigip_member_l2(bigip, loadbalancer, member) if "network_id" not in loadbalancer: LOG.error("update_bigip_l2, expected network ID") return LOG.debug("update_bigip_l2 get network for ID %s" % loadbalancer["network_id"]) loadbalancer['network'] = service_adapter.get_network_from_service( service, loadbalancer['network_id'] ) lb_status = loadbalancer['provisioning_status'] if lb_status == plugin_const.PENDING_DELETE: self.delete_bigip_vip_l2(bigip, loadbalancer) else: LOG.debug("update_bigip_l2 calling update_bigip_vip_l2") self.update_bigip_vip_l2(bigip, loadbalancer) LOG.debug("update_bigip_l2 complete") def update_bigip_member_l2(self, bigip, loadbalancer, member): # update pool member l2 records network = member['network'] if network: if self.l2_service.is_common_network(network): net_folder = 'Common' else: net_folder = self.service_adapter.get_folder_name( loadbalancer['tenant_id'] ) fdb_info = {'network': network, 'ip_address': member['address'], 'mac_address': member['port']['mac_address']} self.l2_service.add_bigip_fdbs( bigip, net_folder, fdb_info, member) def delete_bigip_member_l2(self, bigip, loadbalancer, member): # Delete pool member l2 records network = member['network'] if network: if 'port' in member: if self.l2_service.is_common_network(network): net_folder = 'Common' else: net_folder = self.service_adapter.get_folder_name( loadbalancer['tenant_id'] ) fdb_info = {'network': network, 'ip_address': member['address'], 'mac_address': member['port']['mac_address']} self.l2_service.delete_bigip_fdbs( bigip, net_folder, fdb_info, member) else: LOG.error('Member on SDN has no port. Manual ' 'removal on the BIG-IP will be ' 'required. Was the vm instance ' 'deleted before the pool member ' 'was deleted?') def update_bigip_vip_l2(self, bigip, loadbalancer): # Update vip l2 records network = loadbalancer['network'] if network: if self.l2_service.is_common_network(network): net_folder = 'Common' else: net_folder = self.service_adapter.get_folder_name( loadbalancer['tenant_id'] ) fdb_info = {'network': network, 'ip_address': None, 'mac_address': None} self.l2_service.add_bigip_fdbs( bigip, net_folder, fdb_info, loadbalancer) def delete_bigip_vip_l2(self, bigip, loadbalancer): # Delete loadbalancer l2 records network = loadbalancer['network'] if network: if self.l2_service.is_common_network(network): net_folder = 'Common' else: net_folder = self.service_adapter.get_folder_name( loadbalancer['tenant_id'] ) fdb_info = {'network': network, 'ip_address': None, 'mac_address': None} self.l2_service.delete_bigip_fdbs( bigip, net_folder, fdb_info, loadbalancer) def _assure_delete_nets_shared(self, bigip, service, subnet_hints): # Assure shared configuration (which syncs) is deleted deleted_names = set() tenant_id = service['loadbalancer']['tenant_id'] delete_gateway = self.bigip_selfip_manager.delete_gateway_on_subnet for subnetinfo in self._get_subnets_to_delete(bigip, service, subnet_hints): try: if not self.conf.f5_snat_mode: gw_name = delete_gateway(bigip, subnetinfo) deleted_names.add(gw_name) my_deleted_names, my_in_use_subnets = \ self.bigip_snat_manager.delete_bigip_snats( bigip, subnetinfo, tenant_id) deleted_names = deleted_names.union(my_deleted_names) for in_use_subnetid in my_in_use_subnets: subnet_hints['check_for_delete_subnets'].pop( in_use_subnetid, None) except NeutronException as exc: LOG.error("assure_delete_nets_shared: exception: %s" % str(exc.msg)) except Exception as exc: LOG.error("assure_delete_nets_shared: exception: %s" % str(exc.message)) return deleted_names def _assure_delete_nets_nonshared(self, bigip, service, subnet_hints): # Delete non shared base objects for networks deleted_names = set() for subnetinfo in self._get_subnets_to_delete(bigip, service, subnet_hints): try: network = subnetinfo['network'] if self.l2_service.is_common_network(network): network_folder = 'Common' else: network_folder = self.service_adapter.get_folder_name( service['loadbalancer']['tenant_id']) subnet = subnetinfo['subnet'] if self.conf.f5_populate_static_arp: self.network_helper.arp_delete_by_subnet( bigip, subnet=subnet['cidr'], mask=None, partition=network_folder ) local_selfip_name = "local-" + bigip.device_name + \ "-" + subnet['id'] selfip_address = self.bigip_selfip_manager.get_selfip_addr( bigip, local_selfip_name, partition=network_folder ) if not selfip_address: LOG.error("Failed to get self IP address %s in cleanup.", local_selfip_name) self.bigip_selfip_manager.delete_selfip( bigip, local_selfip_name, partition=network_folder ) if self.l3_binding and selfip_address: self.l3_binding.unbind_address(subnet_id=subnet['id'], ip_address=selfip_address) deleted_names.add(local_selfip_name) self.l2_service.delete_bigip_network(bigip, network) if subnet['id'] not in subnet_hints['do_not_delete_subnets']: subnet_hints['do_not_delete_subnets'].append(subnet['id']) self.remove_from_rds_cache(network, subnet) tenant_id = service['loadbalancer']['tenant_id'] if tenant_id in bigip.assured_tenant_snat_subnets: tenant_snat_subnets = \ bigip.assured_tenant_snat_subnets[tenant_id] if subnet['id'] in tenant_snat_subnets: tenant_snat_subnets.remove(subnet['id']) except NeutronException as exc: LOG.error("assure_delete_nets_nonshared: exception: %s" % str(exc.msg)) except Exception as exc: LOG.error("assure_delete_nets_nonshared: exception: %s" % str(exc.message)) return deleted_names def _get_subnets_to_delete(self, bigip, service, subnet_hints): # Clean up any Self IP, SNATs, networks, and folder for # services items that we deleted. subnets_to_delete = [] for subnetinfo in subnet_hints['check_for_delete_subnets'].values(): subnet = self.service_adapter.get_subnet_from_service( service, subnetinfo['subnet_id']) subnetinfo['subnet'] = subnet network = self.service_adapter.get_network_from_service( service, subnetinfo['network_id']) subnetinfo['network'] = network route_domain = network['route_domain_id'] if not subnet: continue if not self._ips_exist_on_subnet( bigip, service, subnet, route_domain): subnets_to_delete.append(subnetinfo) return subnets_to_delete def _ips_exist_on_subnet(self, bigip, service, subnet, route_domain): # Does the big-ip have any IP addresses on this subnet? LOG.debug("_ips_exist_on_subnet entry %s rd %s" % (str(subnet['cidr']), route_domain)) route_domain = str(route_domain) ipsubnet = netaddr.IPNetwork(subnet['cidr']) # Are there any virtual addresses on this subnet? folder = self.service_adapter.get_folder_name( service['loadbalancer']['tenant_id'] ) virtual_services = self.network_helper.get_virtual_service_insertion( bigip, partition=folder ) for virt_serv in virtual_services: (_, dest) = virt_serv.items()[0] LOG.debug(" _ips_exist_on_subnet: checking vip %s" % str(dest['address'])) if len(dest['address'].split('%')) > 1: vip_route_domain = dest['address'].split('%')[1] else: vip_route_domain = '0' if vip_route_domain != route_domain: continue vip_addr = strip_domain_address(dest['address']) if netaddr.IPAddress(vip_addr) in ipsubnet: LOG.debug(" _ips_exist_on_subnet: found") return True # If there aren't any virtual addresses, are there # node addresses on this subnet? nodes = self.network_helper.get_node_addresses( bigip, partition=folder ) for node in nodes: LOG.debug(" _ips_exist_on_subnet: checking node %s" % str(node)) if len(node.split('%')) > 1: node_route_domain = node.split('%')[1] else: node_route_domain = '0' if node_route_domain != route_domain: continue node_addr = strip_domain_address(node) if netaddr.IPAddress(node_addr) in ipsubnet: LOG.debug(" _ips_exist_on_subnet: found") return True LOG.debug(" _ips_exist_on_subnet exit %s" % str(subnet['cidr'])) # nothing found return False def add_bigip_fdb(self, bigip, fdb): self.l2_service.add_bigip_fdb(bigip, fdb) def remove_bigip_fdb(self, bigip, fdb): self.l2_service.remove_bigip_fdb(bigip, fdb) def update_bigip_fdb(self, bigip, fdb): self.l2_service.update_bigip_fdb(bigip, fdb) def set_context(self, context): self.l2_service.set_context(context) def vlan_exists(self, bigip, network, folder='Common'): return self.vlan_manager.exists(bigip, name=network, partition=folder) def _get_subnets_to_assure(self, service): # Examine service and return active networks networks = dict() loadbalancer = service['loadbalancer'] service_adapter = self.service_adapter lb_status = loadbalancer['provisioning_status'] if lb_status != plugin_const.PENDING_DELETE: if 'network_id' in loadbalancer: network = service_adapter.get_network_from_service( service, loadbalancer['network_id'] ) subnet = service_adapter.get_subnet_from_service( service, loadbalancer['vip_subnet_id'] ) networks[network['id']] = {'network': network, 'subnet': subnet, 'is_for_member': False} for member in service['members']: if member['provisioning_status'] != plugin_const.PENDING_DELETE: if 'network_id' in member: network = service_adapter.get_network_from_service( service, member['network_id'] ) subnet = service_adapter.get_subnet_from_service( service, member['subnet_id'] ) networks[network['id']] = {'network': network, 'subnet': subnet, 'is_for_member': True} return networks.values()
class BigipTenantManager(object): """Create network connectivity for a bigip.""" def __init__(self, conf, driver): """Create a BigipTenantManager.""" self.conf = conf self.driver = driver self.system_helper = SystemHelper() self.network_helper = NetworkHelper() self.service_adapter = self.driver.service_adapter def assure_tenant_created(self, service): """Create tenant partition.""" tenant_id = service["loadbalancer"]["tenant_id"] traffic_group = self.driver.service_to_traffic_group(service) traffic_group = "/Common/" + traffic_group service["traffic_group"] = traffic_group # create tenant folder folder_name = self.service_adapter.get_folder_name(tenant_id) LOG.debug("have folder name %s" % folder_name) for bigip in self.driver.get_config_bigips(): if not self.system_helper.folder_exists(bigip, folder_name): folder = self.service_adapter.get_folder(service) self.system_helper.create_folder(bigip, folder) # folder must sync before route domains are created. self.driver.sync_if_clustered() # create tenant route domain if self.conf.use_namespaces: for bigip in self.driver.get_all_bigips(): if not self.network_helper.route_domain_exists(bigip, folder_name): self.network_helper.create_route_domain(bigip, folder_name, self.conf.f5_route_domain_strictness) def assure_tenant_cleanup(self, service, all_subnet_hints): """Delete tenant partition.""" # Called for every bigip only in replication mode, # otherwise called once. for bigip in self.driver.get_config_bigips(): subnet_hints = all_subnet_hints[bigip.device_name] self._assure_bigip_tenant_cleanup(bigip, service, subnet_hints) # called for every bigip only in replication mode. # otherwise called once def _assure_bigip_tenant_cleanup(self, bigip, service, subnet_hints): tenant_id = service["loadbalancer"]["tenant_id"] if self.conf.f5_sync_mode == "replication": self._remove_tenant_replication_mode(bigip, tenant_id) else: self._remove_tenant_autosync_mode(bigip, tenant_id) def _remove_tenant_replication_mode(self, bigip, tenant_id): # Remove tenant in replication sync-mode partition = self.service_adapter.get_folder_name(tenant_id) domain_names = self.network_helper.get_route_domain_names(bigip, partition) if domain_names: for domain_name in domain_names: self.network_helper.delete_route_domain(bigip, partition, domain_name) # sudslog = std_logging.getLogger('suds.client') # sudslog.setLevel(std_logging.FATAL) self.system_helper.force_root_folder(bigip) # sudslog.setLevel(std_logging.ERROR) try: self.system_helper.delete_folder(bigip, partition) except f5ex.SystemDeleteException: self.system_helper.purge_folder_contents(bigip, partition) self.system_helper.delete_folder(bigip, partition) def _remove_tenant_autosync_mode(self, bigip, tenant_id): partition = self.service_adapter.get_folder_name(tenant_id) # Remove tenant in autosync sync-mode # all domains must be gone before we attempt to delete # the folder or it won't delete due to not being empty for set_bigip in self.driver.get_all_bigips(): self.network_helper.delete_route_domain(set_bigip, partition, None) sudslog = std_logging.getLogger("suds.client") sudslog.setLevel(std_logging.FATAL) self.system_helper.force_root_folder(set_bigip) sudslog.setLevel(std_logging.ERROR) # we need to ensure that the following folder deletion # is clearly the last change that needs to be synced. self.driver.sync_if_clustered() greenthread.sleep(5) try: self.system_helper.delete_folder(bigip, partition) except f5ex.SystemDeleteException: self.system_helper.purge_folder_contents(bigip, partition) self.system_helper.delete_folder(bigip, partition) # Need to make sure this folder delete syncs before # something else runs and changes the current folder to # the folder being deleted which will cause big problems. self.driver.sync_if_clustered()
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 BigipSelfIpManager(object): def __init__(self, driver, l2_service, l3_binding): self.driver = driver self.l2_service = l2_service self.l3_binding = l3_binding self.selfip_manager = BigIPResourceHelper(ResourceType.selfip) self.network_helper = NetworkHelper() def _create_bigip_selfip(self, bigip, model): created = False if self.selfip_manager.exists(bigip, name=model['name'], partition=model['partition']): created = True else: try: self.selfip_manager.create(bigip, model) created = True except HTTPError as err: if (err.response.status_code == 400 and err.response.text.find( "must be one of the vlans " "in the associated route domain") > 0): try: self.network_helper.add_vlan_to_domain( bigip, name=model['vlan'], partition=model['partition']) self.selfip_manager.create(bigip, model) created = True except HTTPError as err: LOG.exception("Error creating selfip %s. " "Repsponse status code: %s. " "Response message: %s." % (model["name"], err.response.status_code, err.message)) raise f5_ex.SelfIPCreationException("selfip") else: LOG.exception("selfip creation error: %s(%s)" % (err.message, err.response.status_code)) raise except Exception as err: LOG.error("Failed to create selfip") LOG.exception(err.message) raise f5_ex.SelfIPCreationException("selfip creation") return created def assure_bigip_selfip(self, bigip, service, subnetinfo): u"""Ensure the BigIP has a selfip address on the tenant subnet.""" network = None subnet = None if 'network' in subnetinfo: network = subnetinfo['network'] if 'subnet' in subnetinfo: subnet = subnetinfo['subnet'] if not network or not subnet: LOG.error('Attempted to create selfip and snats ' 'for network with not id...') raise KeyError("network and subnet need to be specified") tenant_id = service['loadbalancer']['tenant_id'] lb_id = service['loadbalancer']['id'] # If we have already assured this subnet.. return. # Note this cache is periodically cleared in order to # force assurance that the configuration is present. if tenant_id in bigip.assured_tenant_snat_subnets and \ subnet['id'] in bigip.assured_tenant_snat_subnets[tenant_id]: return True selfip_address = self._get_bigip_selfip_address(bigip, subnet, lb_id) if 'route_domain_id' not in network: LOG.error("network route domain is not set") raise KeyError() selfip_address += '%' + str(network['route_domain_id']) if self.l2_service.is_common_network(network): network_folder = 'Common' else: network_folder = self.driver.service_adapter.\ get_folder_name(service['loadbalancer']['tenant_id']) # Get the name of the vlan. (network_name, preserve_network_name) = \ self.l2_service.get_network_name(bigip, network) netmask = netaddr.IPNetwork(subnet['cidr']).prefixlen address = selfip_address + ("/%d" % netmask) model = { "name": "local-" + bigip.device_name + "-" + subnet['id'], "address": address, "vlan": network_name, "floating": "disabled", "partition": network_folder } self._create_bigip_selfip(bigip, model) if self.l3_binding: self.l3_binding.bind_address(subnet_id=subnet['id'], ip_address=selfip_address) def _get_bigip_selfip_address(self, bigip, subnet, device_id): u"""Ensure a selfip address is allocated on Neutron network.""" # Get ip address for selfip to use on BIG-IP. if self.driver.conf.unlegacy_setting_placeholder: LOG.debug('setting vnic_type to normal instead of baremetal') vnic_type = "normal" else: vnic_type = "baremetal" selfip_address = "" selfip_name = "local-" + bigip.device_name + "-" + subnet['id'] ports = self.driver.plugin_rpc.get_port_by_name(port_name=selfip_name) if len(ports) > 0: port = ports[0] else: port = self.driver.plugin_rpc.create_port_on_subnet( subnet_id=subnet['id'], mac_address=None, name=selfip_name, fixed_address_count=1, device_id=device_id, vnic_type=vnic_type) if port and 'fixed_ips' in port: fixed_ip = port['fixed_ips'][0] selfip_address = fixed_ip['ip_address'] return selfip_address def assure_gateway_on_subnet(self, bigip, subnetinfo, traffic_group): """Ensure """ network = None subnet = None if 'network' in subnetinfo: network = subnetinfo['network'] if 'subnet' in subnetinfo: subnet = subnetinfo['subnet'] if not network or not subnet: raise KeyError("network and subnet must be specified to create " "gateway on subnet.") if not subnet['gateway_ip']: raise KeyError("attempting to create gateway on subnet without " "gateway ip address specified.") if subnet['id'] in bigip.assured_gateway_subnets: return True (network_name, preserve_network_name) = \ self.l2_service.get_network_name(bigip, network) if self.l2_service.is_common_network(network): network_folder = 'Common' else: network_folder = self.driver.service_adapter.\ get_folder_name(subnet['tenant_id']) # Create a floating SelfIP for the given traffic-group. floating_selfip_name = "gw-" + subnet['id'] netmask = netaddr.IPNetwork(subnet['cidr']).prefixlen address = subnet['gateway_ip'] + "%" + str(network['route_domain_id']) address += ("/%d" % (netmask)) model = { 'name': floating_selfip_name, 'address': address, 'vlan': network_name, 'floating': True, 'traffic-group': traffic_group, 'partition': network_folder } if not self._create_bigip_selfip(bigip, model): LOG.error("failed to create gateway selfip") if self.l3_binding: self.l3_binding.bind_address(subnet_id=subnet['id'], ip_address=subnet['gateway_ip']) # Setup a wild card ip forwarding virtual service for this subnet gw_name = "gw-" + subnet['id'] vs = bigip.tm.ltm.virtuals.virtual if not vs.exists(name=gw_name, partition=network_folder): try: vs.create(name=gw_name, partition=network_folder, destination='0.0.0.0:0', mask='0.0.0.0', vlansEnabled=True, vlans=[network_name], sourceAddressTranslation={'type': 'automap'}, ipForward=True) except Exception as err: LOG.exception(err) raise f5_ex.VirtualServerCreationException( "Failed to create gateway virtual service on subnet %s", subnet['id']) # Put the virtual server address in the specified traffic group virtual_address = bigip.tm.ltm.virtual_address_s.virtual_address try: obj = virtual_address.load(name='0.0.0.0', partition=network_folder) obj.modify(trafficGroup=traffic_group) except Exception as err: LOG.exception(err) raise f5_ex.VirtualServerCreationException( "Failed to add virtual address to traffic group %s", traffic_group) bigip.assured_gateway_subnets.append(subnet['id']) def delete_gateway_on_subnet(self, bigip, subnetinfo): network = None subnet = None if 'network' in subnetinfo: network = subnetinfo['network'] if 'subnet' in subnetinfo: subnet = subnetinfo['subnet'] if not network or not subnet: LOG.error('Attempted to create selfip and snats ' 'for network with no id...') raise KeyError("network and subnet must be specified") if not subnet['gateway_ip']: raise KeyError("attempting to create gateway on subnet without " "gateway ip address specified.") if self.l2_service.is_common_network(network): network_folder = 'Common' else: network_folder = self.driver.service_adapter.\ get_folder_name(subnet['tenant_id']) if self.driver.conf.f5_populate_static_arp: self.network_helper.arp_delete_by_subnet( bigip, partition=network_folder, subnet=subnetinfo['subnet']['cidr'], mask=None) floating_selfip_name = "gw-" + subnet['id'] self.delete_selfip(bigip, floating_selfip_name, network_folder) if self.l3_binding: self.l3_binding.unbind_address(subnet_id=subnet['id'], ip_address=subnet['gateway_ip']) gw_name = "gw-" + subnet['id'] vs = bigip.tm.ltm.virtuals.virtual try: if vs.exists(name=gw_name, partition=network_folder): obj = vs.load(name=gw_name, partition=network_folder) obj.delete() except Exception as err: LOG.exception(err) raise f5_ex.VirtualServerDeleteException( "Failed to delete gateway service on subnet %s", subnet['id']) if subnet['id'] in bigip.assured_gateway_subnets: bigip.assured_gateway_subnets.remove(subnet['id']) return gw_name def get_selfip_addr(self, bigip, name, partition=const.DEFAULT_PARTITION): selfip_addr = "" try: s = bigip.tm.net.selfips.selfip if s.exists(name=name, partition=partition): obj = s.load(name=name, partition=partition) # The selfip address on BigIP is actually a network, # parse out the address portion. if obj.address: (selfip_addr, netbits) = obj.address.split("/") except HTTPError as err: LOG.exception("Error getting selfip address for %s. " "Repsponse status code: %s. Response " "message: %s." % (name, err.response.status_code, err.message)) except Exception as err: LOG.exception("Error getting selfip address for %s.", name) return selfip_addr def get_selfips(self, bigip, partition=const.DEFAULT_PARTITION, vlan_name=None): selfips_list = [] if vlan_name: if not vlan_name.startswith('/'): vlan_name = "/%s/%s" % (partition, vlan_name) params = {'params': get_filter(bigip, 'partition', 'eq', partition)} try: selfips_list = [ selfip for selfip in bigip.tm.net.selfips.get_collection( requests_params=params) if vlan_name == selfip.vlan or not vlan_name ] except HTTPError as err: LOG.exception("Error getting selfips for vlan(%s). " "Response status code: %s. " "Response message: %s." % (vlan_name, err.response.status_code, err.message)) raise f5_ex.SelfIPQueryException( "Failed to get selfips assigned to vlan") return selfips_list def delete_selfip(self, bigip, name, partition=const.DEFAULT_PARTITION): """Delete the selfip if it exists.""" try: s = bigip.tm.net.selfips.selfip if s.exists(name=name, partition=partition): obj = s.load(name=name, partition=partition) obj.delete() except HTTPError as err: LOG.exception("Error deleting selfip %s. " "Response status code: %s. Response " "message: %s." % (name, err.response.status_code, err.message)) raise f5_ex.SelfIPDeleteException("Failed to delete selfip %s." % name) except Exception as err: raise f5_ex.SelfIPDeleteException("Failed to delete selfip %s." % name)
class BigipTenantManager(object): """Create network connectivity for a bigip.""" def __init__(self, conf, driver): # XXX maybe we need a better name: conf """Create a BigipTenantManager.""" self.conf = conf self.driver = driver self.system_helper = SystemHelper() self.network_helper = NetworkHelper() self.service_adapter = self.driver.service_adapter def assure_tenant_created(self, service): """Create tenant partition. This method modifies its argument 'service' in place. This method adds a 'traffic_group" key to the service dict with a value of traffic_group_string_id. But aren't traffic_groups a bigIP device concept? And wasn't (until this method was called) the service object a configuration entirely defined by neutron? Also for neutron->device adaptations shouldn't we be using ServiceModelAdapter... though I suppose this is the other way. """ tenant_id = service['loadbalancer']['tenant_id'] traffic_group = self.driver.service_to_traffic_group(service) traffic_group = '/Common/' + traffic_group service["traffic_group"] = traffic_group # modify the passed dict # create tenant folder folder_name = self.service_adapter.get_folder_name(tenant_id) LOG.debug("Creating tenant folder %s" % folder_name) for bigip in self.driver.get_config_bigips(): if not self.system_helper.folder_exists(bigip, folder_name): folder = self.service_adapter.get_folder(service) # This folder is a dict config obj, that can be passed to # folder.create in the SDK try: self.system_helper.create_folder(bigip, folder) except Exception as err: # XXX Maybe we can make this more specific? LOG.exception("Error creating folder %s" % (folder)) raise f5ex.SystemCreationException( "Folder creation error for tenant %s" % (tenant_id)) if not self.driver.disconnected_service.network_exists( bigip, folder_name): try: self.driver.disconnected_service.create_network( bigip, folder_name) except Exception as err: LOG.exception("Error creating disconnected network %s." % (folder_name)) raise f5ex.SystemCreationException( "Disconnected network create error for tenant %s" % (tenant_id)) # create tenant route domain if self.conf.use_namespaces: for bigip in self.driver.get_all_bigips(): if not self.network_helper.route_domain_exists(bigip, folder_name): try: self.network_helper.create_route_domain( bigip, folder_name, self.conf.f5_route_domain_strictness) except Exception as err: LOG.exception(err.message) raise f5ex.RouteDomainCreationException( "Failed to create route domain for " "tenant in %s" % (folder_name)) def assure_tenant_cleanup(self, service, all_subnet_hints): """Delete tenant partition.""" # Called for every bigip only in replication mode, # otherwise called once. for bigip in self.driver.get_config_bigips(): subnet_hints = all_subnet_hints[bigip.device_name] self._assure_bigip_tenant_cleanup(bigip, service, subnet_hints) # called for every bigip only in replication mode. # otherwise called once def _assure_bigip_tenant_cleanup(self, bigip, service, subnet_hints): tenant_id = service['loadbalancer']['tenant_id'] self._remove_tenant_replication_mode(bigip, tenant_id) def _remove_tenant_replication_mode(self, bigip, tenant_id): # Remove tenant in replication sync-mode partition = self.service_adapter.get_folder_name(tenant_id) domain_names = self.network_helper.get_route_domain_names(bigip, partition) for domain_name in domain_names: try: self.network_helper.delete_route_domain(bigip, partition, domain_name) except Exception as err: LOG.error("Failed to delete route domain %s. " "%s. Manual intervention might be required." % (domain_name, err.message)) if self.driver.disconnected_service.network_exists( bigip, partition): try: self.driver.disconnected_service.delete_network(bigip, partition) except Exception as err: LOG.error("Failed to delete disconnected network %s. " "%s. Manual intervention might be required." % (partition, err.message)) try: self.system_helper.delete_folder(bigip, partition) except Exception as err: LOG.error( "Folder deletion exception for tenant partition %s occurred." % tenant_id) LOG.exception("%s" % err.message) raise f5ex.SystemDeleteException( "Failed to destroy folder %s manual cleanup might be " "required." % partition)
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}
def __init__(self, driver, l2_service, l3_binding): self.driver = driver self.l2_service = l2_service self.l3_binding = l3_binding self.selfip_manager = BigIPResourceHelper(ResourceType.selfip) self.network_helper = NetworkHelper()
class BigipSelfIpManager(object): def __init__(self, driver, l2_service, l3_binding): self.driver = driver self.l2_service = l2_service self.l3_binding = l3_binding self.selfip_manager = BigIPResourceHelper(ResourceType.selfip) self.network_helper = NetworkHelper() def _create_bigip_selfip(self, bigip, model): created = False if self.selfip_manager.exists(bigip, name=model['name'], partition=model['partition']): created = True else: try: self.selfip_manager.create(bigip, model) created = True except HTTPError as err: if (err.response.status_code == 400 and err.response.text.find( "must be one of the vlans " "in the associated route domain") > 0): try: self.network_helper.add_vlan_to_domain( bigip, name=model['vlan'], partition=model['partition']) self.selfip_manager.create(bigip, model) created = True except HTTPError as err: LOG.exception("Error creating selfip %s. " "Repsponse status code: %s. " "Response message: %s." % ( model["name"], err.response.status_code, err.message)) raise f5_ex.SelfIPCreationException("selfip") else: LOG.exception("selfip creation error: %s(%s)" % (err.message, err.response.status_code)) raise except Exception as err: LOG.error("Failed to create selfip") LOG.exception(err.message) raise f5_ex.SelfIPCreationException("selfip creation") return created def assure_bigip_selfip(self, bigip, service, subnetinfo): u"""Ensure the BigIP has a selfip address on the tenant subnet.""" network = None subnet = None if 'network' in subnetinfo: network = subnetinfo['network'] if 'subnet' in subnetinfo: subnet = subnetinfo['subnet'] if not network or not subnet: LOG.error('Attempted to create selfip and snats ' 'for network with not id...') raise KeyError("network and subnet need to be specified") tenant_id = service['loadbalancer']['tenant_id'] lb_id = service['loadbalancer']['id'] # If we have already assured this subnet.. return. # Note this cache is periodically cleared in order to # force assurance that the configuration is present. if tenant_id in bigip.assured_tenant_snat_subnets and \ subnet['id'] in bigip.assured_tenant_snat_subnets[tenant_id]: return True selfip_address = self._get_bigip_selfip_address(bigip, subnet, lb_id) if 'route_domain_id' not in network: LOG.error("network route domain is not set") raise KeyError() selfip_address += '%' + str(network['route_domain_id']) if self.l2_service.is_common_network(network): network_folder = 'Common' else: network_folder = self.driver.service_adapter.\ get_folder_name(service['loadbalancer']['tenant_id']) # Get the name of the vlan. (network_name, preserve_network_name) = \ self.l2_service.get_network_name(bigip, network) netmask = netaddr.IPNetwork(subnet['cidr']).prefixlen address = selfip_address + ("/%d" % netmask) model = { "name": "local-" + bigip.device_name + "-" + subnet['id'], "address": address, "vlan": network_name, "floating": "disabled", "partition": network_folder } self._create_bigip_selfip(bigip, model) if self.l3_binding: self.l3_binding.bind_address(subnet_id=subnet['id'], ip_address=selfip_address) def _get_bigip_selfip_address(self, bigip, subnet, device_id): u"""Ensure a selfip address is allocated on Neutron network.""" # Get ip address for selfip to use on BIG-IP. selfip_address = "" selfip_name = "local-" + bigip.device_name + "-" + subnet['id'] ports = self.driver.plugin_rpc.get_port_by_name(port_name=selfip_name) if len(ports) > 0: port = ports[0] else: port = self.driver.plugin_rpc.create_port_on_subnet( subnet_id=subnet['id'], mac_address=None, name=selfip_name, fixed_address_count=1, device_id=device_id ) if port and 'fixed_ips' in port: fixed_ip = port['fixed_ips'][0] selfip_address = fixed_ip['ip_address'] return selfip_address def assure_gateway_on_subnet(self, bigip, subnetinfo, traffic_group): """Ensure """ network = None subnet = None if 'network' in subnetinfo: network = subnetinfo['network'] if 'subnet' in subnetinfo: subnet = subnetinfo['subnet'] if not network or not subnet: raise KeyError("network and subnet must be specified to create " "gateway on subnet.") if not subnet['gateway_ip']: raise KeyError("attempting to create gateway on subnet without " "gateway ip address specified.") if subnet['id'] in bigip.assured_gateway_subnets: return True (network_name, preserve_network_name) = \ self.l2_service.get_network_name(bigip, network) if self.l2_service.is_common_network(network): network_folder = 'Common' else: network_folder = self.driver.service_adapter.\ get_folder_name(subnet['tenant_id']) # Create a floating SelfIP for the given traffic-group. floating_selfip_name = "gw-" + subnet['id'] netmask = netaddr.IPNetwork(subnet['cidr']).prefixlen address = subnet['gateway_ip'] + "%" + str(network['route_domain_id']) address += ("/%d" % (netmask)) model = { 'name': floating_selfip_name, 'address': address, 'vlan': network_name, 'floating': True, 'traffic-group': traffic_group, 'partition': network_folder } if not self._create_bigip_selfip(bigip, model): LOG.error("failed to create gateway selfip") if self.l3_binding: self.l3_binding.bind_address(subnet_id=subnet['id'], ip_address=subnet['gateway_ip']) # Setup a wild card ip forwarding virtual service for this subnet gw_name = "gw-" + subnet['id'] vs = bigip.tm.ltm.virtuals.virtual if not vs.exists(name=gw_name, partition=network_folder): try: vs.create( name=gw_name, partition=network_folder, destination='0.0.0.0:0', mask='0.0.0.0', vlansEnabled=True, vlans=[network_name], sourceAddressTranslation={'type': 'automap'}, ipForward=True ) except Exception as err: LOG.exception(err) raise f5_ex.VirtualServerCreationException( "Failed to create gateway virtual service on subnet %s", subnet['id'] ) # Put the virtual server address in the specified traffic group virtual_address = bigip.tm.ltm.virtual_address_s.virtual_address try: obj = virtual_address.load( name='0.0.0.0', partition=network_folder) obj.modify(trafficGroup=traffic_group) except Exception as err: LOG.exception(err) raise f5_ex.VirtualServerCreationException( "Failed to add virtual address to traffic group %s", traffic_group) bigip.assured_gateway_subnets.append(subnet['id']) def delete_gateway_on_subnet(self, bigip, subnetinfo): network = None subnet = None if 'network' in subnetinfo: network = subnetinfo['network'] if 'subnet' in subnetinfo: subnet = subnetinfo['subnet'] if not network or not subnet: LOG.error('Attempted to create selfip and snats ' 'for network with no id...') raise KeyError("network and subnet must be specified") if not subnet['gateway_ip']: raise KeyError("attempting to create gateway on subnet without " "gateway ip address specified.") if self.l2_service.is_common_network(network): network_folder = 'Common' else: network_folder = self.driver.service_adapter.\ get_folder_name(subnet['tenant_id']) if self.driver.conf.f5_populate_static_arp: self.network_helper.arp_delete_by_subnet( bigip, partition=network_folder, subnet=subnetinfo['subnet']['cidr'], mask=None ) floating_selfip_name = "gw-" + subnet['id'] self.delete_selfip( bigip, floating_selfip_name, network_folder) if self.l3_binding: self.l3_binding.unbind_address(subnet_id=subnet['id'], ip_address=subnet['gateway_ip']) gw_name = "gw-" + subnet['id'] vs = bigip.tm.ltm.virtuals.virtual try: if vs.exists(name=gw_name, partition=network_folder): obj = vs.load(name=gw_name, partition=network_folder) obj.delete() except Exception as err: LOG.exception(err) raise f5_ex.VirtualServerDeleteException( "Failed to delete gateway service on subnet %s", subnet['id']) if subnet['id'] in bigip.assured_gateway_subnets: bigip.assured_gateway_subnets.remove(subnet['id']) return gw_name def get_selfip_addr(self, bigip, name, partition=const.DEFAULT_PARTITION): selfip_addr = "" try: s = bigip.tm.net.selfips.selfip if s.exists(name=name, partition=partition): obj = s.load(name=name, partition=partition) # The selfip address on BigIP is actually a network, # parse out the address portion. if obj.address: (selfip_addr, netbits) = obj.address.split("/") except HTTPError as err: LOG.exception("Error getting selfip address for %s. " "Repsponse status code: %s. Response " "message: %s." % (name, err.response.status_code, err.message)) except Exception as err: LOG.exception("Error getting selfip address for %s.", name) return selfip_addr def get_selfips(self, bigip, partition=const.DEFAULT_PARTITION, vlan_name=None): selfips_list = [] if vlan_name: if not vlan_name.startswith('/'): vlan_name = "/%s/%s" % (partition, vlan_name) params = {'params': get_filter(bigip, 'partition', 'eq', partition)} try: selfips_list = [selfip for selfip in bigip.tm.net.selfips.get_collection( requests_params=params ) if vlan_name == selfip.vlan or not vlan_name] except HTTPError as err: LOG.exception("Error getting selfips for vlan(%s). " "Response status code: %s. " "Response message: %s." % ( vlan_name, err.response.status_code, err.message)) raise f5_ex.SelfIPQueryException( "Failed to get selfips assigned to vlan") return selfips_list def delete_selfip(self, bigip, name, partition=const.DEFAULT_PARTITION): """Delete the selfip if it exists.""" try: s = bigip.tm.net.selfips.selfip if s.exists(name=name, partition=partition): obj = s.load(name=name, partition=partition) obj.delete() except HTTPError as err: LOG.exception("Error deleting selfip %s. " "Response status code: %s. Response " "message: %s." % (name, err.response.status_code, err.message)) raise f5_ex.SelfIPDeleteException( "Failed to delete selfip %s." % name) except Exception as err: raise f5_ex.SelfIPDeleteException( "Failed to delete selfip %s." % name)
def __init__(self): self.network_name = DisconnectedService.network_name self.network_helper = NetworkHelper()
import pytest import requests from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) from f5_openstack_agent.lbaasv2.drivers.bigip.network_helper import \ NetworkHelper from f5_openstack_agent.lbaasv2.drivers.bigip.system_helper import \ SystemHelper from pprint import pprint from requests.exceptions import HTTPError network_helper = NetworkHelper() system_helper = SystemHelper() default_partition = 'test' def log_test_call(func): def wrapper(func, *args, **kwargs): print("\nRunning %s" % func.func_name) return func(*args, **kwargs) return decorator.decorator(wrapper, func) def delete_resource(resource): try: resource.delete() except HTTPError as err:
class BigipTenantManager(object): """Create network connectivity for a bigip.""" def __init__(self, conf, driver): # XXX maybe we need a better name: conf """Create a BigipTenantManager.""" self.conf = conf self.driver = driver self.system_helper = SystemHelper() self.network_helper = NetworkHelper() self.service_adapter = self.driver.service_adapter def assure_tenant_created(self, service): """Create tenant partition. This method modifies its argument 'service' in place. This method adds a 'traffic_group" key to the service dict with a value of traffic_group_string_id. But aren't traffic_groups a bigIP device concept? And wasn't (until this method was called) the service object a configuration entirely defined by neutron? Also for neutron->device adaptations shouldn't we be using ServiceModelAdapter... though I suppose this is the other way. """ tenant_id = service['loadbalancer']['tenant_id'] traffic_group = self.driver.service_to_traffic_group(service) traffic_group = '/Common/' + traffic_group service["traffic_group"] = traffic_group # modify the passed dict # create tenant folder folder_name = self.service_adapter.get_folder_name(tenant_id) LOG.debug("Creating tenant folder %s" % folder_name) for bigip in self.driver.get_config_bigips(): if not self.system_helper.folder_exists(bigip, folder_name): folder = self.service_adapter.get_folder(service) # This folder is a dict config obj, that can be passed to # folder.create in the SDK try: self.system_helper.create_folder(bigip, folder) except Exception as err: # XXX Maybe we can make this more specific? LOG.exception("Error creating folder %s" % (folder)) raise f5ex.SystemCreationException( "Folder creation error for tenant %s" % (tenant_id)) # create tenant route domain if self.conf.use_namespaces: for bigip in self.driver.get_all_bigips(): if not self.network_helper.route_domain_exists( bigip, folder_name): try: self.network_helper.create_route_domain( bigip, folder_name, self.conf.f5_route_domain_strictness) except Exception as err: LOG.exception(err.message) raise f5ex.RouteDomainCreationException( "Failed to create route domain for " "tenant in %s" % (folder_name)) def assure_tenant_cleanup(self, service, all_subnet_hints): """Delete tenant partition.""" # Called for every bigip only in replication mode, # otherwise called once. for bigip in self.driver.get_config_bigips(): subnet_hints = all_subnet_hints[bigip.device_name] self._assure_bigip_tenant_cleanup(bigip, service, subnet_hints) # called for every bigip only in replication mode. # otherwise called once def _assure_bigip_tenant_cleanup(self, bigip, service, subnet_hints): tenant_id = service['loadbalancer']['tenant_id'] self._remove_tenant_replication_mode(bigip, tenant_id) def _remove_tenant_replication_mode(self, bigip, tenant_id): # Remove tenant in replication sync-mode partition = self.service_adapter.get_folder_name(tenant_id) domain_names = self.network_helper.get_route_domain_names( bigip, partition) for domain_name in domain_names: try: self.network_helper.delete_route_domain( bigip, partition, domain_name) except Exception as err: LOG.debug("Failed to delete route domain %s. " "%s. Manual intervention might be required." % (domain_name, err.message)) try: self.system_helper.delete_folder(bigip, partition) except Exception as err: LOG.debug( "Folder deletion exception for tenant partition %s occurred. " "Manual cleanup might be required." % (tenant_id))
class BigipSelfIpManager(object): def __init__(self, driver, l2_service, l3_binding): self.driver = driver self.l2_service = l2_service self.l3_binding = l3_binding self.selfip_manager = BigIPResourceHelper(ResourceType.selfip) self.network_helper = NetworkHelper() def create_bigip_selfip(self, bigip, model): if not model['name']: return False LOG.debug("Getting selfip....") s = bigip.net.selfips.selfip if s.exists(name=model['name'], partition=model['partition']): LOG.debug("It exists!!!!") return True try: LOG.debug("Doesn't exist!!!!") self.selfip_manager.create(bigip, model) LOG.debug("CREATED!!!!") except HTTPError as err: if err.response.status_code is not 400: raise if err.response.text.find("must be one of the vlans " "in the associated route domain") > 0: self.network_helper.add_vlan_to_domain( bigip, name=model['vlan'], partition=model['partition']) try: self.selfip_manager.create(bigip, model) except HTTPError as err: LOG.error("Error creating selfip %s. " "Repsponse status code: %s. Response " "message: %s." % (model["name"], err.response.status_code, err.message)) def assure_bigip_selfip(self, bigip, service, subnetinfo): network = subnetinfo['network'] if not network: LOG.error('Attempted to create selfip and snats ' 'for network with no id... skipping.') return subnet = subnetinfo['subnet'] tenant_id = service['loadbalancer']['tenant_id'] # If we have already assured this subnet.. return. # Note this cache is periodically cleared in order to # force assurance that the configuration is present. if tenant_id in bigip.assured_tenant_snat_subnets and \ subnet['id'] in bigip.assured_tenant_snat_subnets[tenant_id]: return selfip_address = self._get_bigip_selfip_address(bigip, subnet) # FIXME(Rich Browne): it is possible this is not set unless # use namespaces is true. I think this method is only called # in the global_routed_mode == False case though. Need to check # that network['route_domain_id'] exists. if 'route_domain_id' not in network: LOG.debug("NETWORK ROUTE DOMAIN NOT SET") network['route_domain_id'] = "0" LOG.debug("route domain id: %s" % network['route_domain_id']) selfip_address += '%' + str(network['route_domain_id']) LOG.debug("have selfip address: %s" % selfip_address) if self.l2_service.is_common_network(network): network_folder = 'Common' else: network_folder = self.driver.service_adapter.\ get_folder_name(service['loadbalancer']['tenant_id']) LOG.debug("getting network name") (network_name, preserve_network_name) = \ self.l2_service.get_network_name(bigip, network) LOG.debug("CREATING THE SELFIP--------------------") netmask = netaddr.IPNetwork(subnet['cidr']).prefixlen address = selfip_address + ("/%d" % netmask) model = { "name": "local-" + bigip.device_name + "-" + subnet['id'], "address": address, "vlan": network_name, "floating": "disabled", "partition": network_folder } LOG.debug("Model: %s" % model) self.create_bigip_selfip(bigip, model) # TO DO: we need to only bind the local SelfIP to the # local device... not treat it as if it was floating LOG.debug("self ip CREATED!!!!!!") if self.l3_binding: self.l3_binding.bind_address(subnet_id=subnet['id'], ip_address=selfip_address) def _get_bigip_selfip_address(self, bigip, subnet): # Get ip address for selfip to use on BIG-IP®. selfip_name = "local-" + bigip.device_name + "-" + subnet['id'] ports = self.driver.plugin_rpc.get_port_by_name(port_name=selfip_name) if len(ports) > 0: port = ports[0] else: port = self.driver.plugin_rpc.create_port_on_subnet( subnet_id=subnet['id'], mac_address=None, name=selfip_name, fixed_address_count=1) return port['fixed_ips'][0]['ip_address'] def assure_gateway_on_subnet(self, bigip, subnetinfo, traffic_group): # Called for every bigip only in replication mode. # Otherwise called once. subnet = subnetinfo['subnet'] if subnet['id'] in bigip.assured_gateway_subnets: return network = subnetinfo['network'] (network_name, preserve_network_name) = \ self.l2_service.get_network_name(bigip, network) if self.l2_service.is_common_network(network): network_folder = 'Common' network_name = '/Common/' + network_name else: network_folder = self.driver.service_adapter.\ get_folder_name(subnet['tenant_id']) # Select a traffic group for the floating SelfIP floating_selfip_name = "gw-" + subnet['id'] netmask = netaddr.IPNetwork(subnet['cidr']).netmask model = { 'name': floating_selfip_name, 'ip_address': subnet['gateway_ip'], 'netmask': netmask, 'vlan_name': network_name, 'floating': True, 'traffic_group': traffic_group, 'partition': network_folder, 'preserve_vlan_name': preserve_network_name } self.create_bigip_selfip(bigip, model) if self.l3_binding: self.l3_binding.bind_address(subnet_id=subnet['id'], ip_address=subnet['gateway_ip']) # Setup a wild card ip forwarding virtual service for this subnet gw_name = "gw-" + subnet['id'] vs = bigip.ltm.virtuals.virtual if not vs.exists(name=gw_name, partition=network_folder): vs.create( name=gw_name, partition=network_folder, destination='0.0.0.0:0', mask='0.0.0.0', vlansEnabled=True, vlans=[network_name], sourceAddressTranslation={'type': 'automap'}, ipForward=True ) else: vs.load(name=gw_name, partition=network_folder) virtual_address = bigip.ltm.virtual_address_s.virtual_address virtual_address.load(name='0.0.0.0:0', partition=network_folder) virtual_address.update(trafficGroup=traffic_group) bigip.assured_gateway_subnets.append(subnet['id']) def delete_gateway_on_subnet(self, bigip, subnetinfo): # Called for every bigip only in replication mode. # Otherwise called once. network = subnetinfo['network'] if not network: LOG.error('Attempted to delete default gateway ' 'for network with no id... skipping.') return subnet = subnetinfo['subnet'] if self.l2_service.is_common_network(network): network_folder = 'Common' else: network_folder = self.driver.service_adapter.\ get_folder_name(subnet['tenant_id']) floating_selfip_name = "gw-" + subnet['id'] if self.driver.conf.f5_populate_static_arp: self.network_helper.arp_delete_by_subnet( bigip, partition=network_folder, subnet=subnetinfo['subnet']['cidr'], mask=None ) self.network_helper.delete_selfip( bigip, floating_selfip_name, network_folder) if self.l3_binding: self.l3_binding.unbind_address(subnet_id=subnet['id'], ip_address=subnet['gateway_ip']) gw_name = "gw-" + subnet['id'] vs = bigip.ltm.virtuals.virtual if vs.exists(name=gw_name, partition=network_folder): vs.load(name=gw_name, partition=network_folder) vs.delete() if subnet['id'] in bigip.assured_gateway_subnets: bigip.assured_gateway_subnets.remove(subnet['id']) return gw_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