def __init__(self, conf, driver, bigip_l2_manager=None, l3_binding=None): self.conf = conf self.driver = driver self.bigip_l2_manager = bigip_l2_manager self.l3_binding = l3_binding self.bigip_selfip_manager = BigipSelfIpManager(driver, bigip_l2_manager, l3_binding) self.bigip_snat_manager = BigipSnatManager(driver, bigip_l2_manager, l3_binding) self.rds_cache = {}
def __init__(self, conf, driver, bigip_l2_manager=None, l3_binding=None): self.conf = conf self.driver = driver self.bigip_l2_manager = bigip_l2_manager self.l3_binding = l3_binding self.bigip_selfip_manager = BigipSelfIpManager( driver, bigip_l2_manager, l3_binding) self.bigip_snat_manager = BigipSnatManager( driver, bigip_l2_manager, l3_binding) self.rds_cache = {}
class NetworkBuilderDirect(object): """Create network connectivity for a bigip """ def __init__(self, conf, driver, bigip_l2_manager=None, l3_binding=None): self.conf = conf self.driver = driver self.bigip_l2_manager = bigip_l2_manager self.l3_binding = l3_binding self.bigip_selfip_manager = BigipSelfIpManager(driver, bigip_l2_manager, l3_binding) self.bigip_snat_manager = BigipSnatManager(driver, bigip_l2_manager, l3_binding) self.rds_cache = {} def initialize_tunneling(self): """ setup tunneling setup VTEP tunnels if needed """ 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 bigip.vxlan.create_multipoint_profile(name='vxlan_ovs', folder='Common') bigip.l2gre.create_multipoint_profile(name='gre_ovs', folder='Common') # find the IP address for the selfip for each box local_ip = bigip.selfip.get_addr(vtep_selfip_name, vtep_folder) if local_ip: bigip.local_ip = local_ip local_ips.append(local_ip) else: raise f5ex.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 for the service. """ if self.conf.f5_global_routed_mode or not service['pool']: return if self.conf.use_namespaces: self._annotate_service_route_domains(service) # Per Device Network Connectivity (VLANs or Tunnels) subnetsinfo = _get_subnets_to_assure(service) for (assure_bigip, subnetinfo) in \ itertools.product(self.driver.get_all_bigips(), subnetsinfo): self.bigip_l2_manager.assure_bigip_network(assure_bigip, subnetinfo['network']) self.bigip_selfip_manager.assure_bigip_selfip( assure_bigip, service, subnetinfo) # L3 Shared Config assure_bigips = self.driver.get_config_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: self._allocate_gw_addr(subnetinfo) 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['pool']['tenant_id'] self.update_rds_cache(tenant_id) if 'members' in service: for member in service['members']: LOG.debug("processing member %s" % member['address']) if 'address' in member: if 'network' in member and 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' in service and 'address' in service['vip']: vip = service['vip'] if 'network' in vip and vip['network']: self.assign_route_domain(tenant_id, vip['network'], vip['subnet']) rd_id = '%' + str(vip['network']['route_domain_id']) service['vip']['address'] += rd_id else: service['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.bigip_l2_manager.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) tenant_rd = bigip.route.get_domain(folder=tenant_id) network['route_domain_id'] = tenant_rd 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(): # folder = bigip.decorate_folder(tenant_id) bigip_id = bigip.route.create_domain( folder=tenant_id, strict_route_isolation=self.conf.f5_route_domain_strictness, is_aux=True) if route_domain_id is None: route_domain_id = bigip_id elif bigip_id != route_domain_id: LOG.debug( "Bigips allocated two different route domains!: %s %s" % (bigip_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 = bigip.route.get_domain_ids(folder=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 rd_vlans = bigip.route.get_vlans_in_domain_by_id( folder=tenant_id, route_domain_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: 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'] selfips = bigip.selfip.get_selfips(folder=tenant_id, vlan=rd_vlan) LOG.debug("rds_cache: got selfips: %s" % 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 = selfip['address'].split('/')[0] addr = addr.split('%')[0] netbits = selfip['address'].split('/')[1] 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 """ 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]: net_entry = tenant_cache[route_domain_id][net_short_name] if subnet['id'] in net_entry: del net_entry[subnet['id']] @staticmethod def get_bigip_net_short_name(bigip, tenant_id, network_name): """ Return <network_type>-<seg_id> for bigip network """ if '_tunnel-gre-' in network_name: tunnel_key = bigip.l2gre.get_tunnel_key(name=network_name, folder=tenant_id) return 'gre-%s' % tunnel_key elif '_tunnel-vxlan-' in network_name: tunnel_key = bigip.vxlan.get_tunnel_key(name=network_name, folder=tenant_id) return 'vxlan-%s' % tunnel_key else: vlan_id = bigip.vlan.get_id(name=network_name, folder=tenant_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['pool']['tenant_id'] subnet = subnetinfo['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]] if len(assure_bigips): snat_addrs = self.bigip_snat_manager.get_snat_addrs( subnetinfo, tenant_id) 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 """ network = subnetinfo['network'] if not network: LOG.error( _('Attempted to create default gateway' ' for network with no id.. skipping.')) return subnet = subnetinfo['subnet'] 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.error(_(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)) # avoids race condition: # deletion of shared ip objects must sync before we # remove the selfips or vlans from the peer bigips. self.driver.sync_if_clustered() # 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) if self.conf.f5_sync_mode == 'replication': subnet_hints = all_subnet_hints[bigip.device_name] else: # If in autosync mode, then the IP operations were performed # on just the primary big-ip, and so that is where the subnet # hints are stored. So, just use those hints for every bigip. device_name = self.driver.get_bigip().device_name subnet_hints = all_subnet_hints[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 """ vip = service['vip'] pool = service['pool'] for bigip in self.driver.get_all_bigips(): for member in service['members']: if member['status'] == plugin_const.PENDING_DELETE: self.delete_bigip_member_l2(bigip, pool, member) else: self.update_bigip_member_l2(bigip, pool, member) if 'id' in vip: if vip['status'] == plugin_const.PENDING_DELETE: self.delete_bigip_vip_l2(bigip, vip) else: self.update_bigip_vip_l2(bigip, vip) def update_bigip_member_l2(self, bigip, pool, member): """ update pool member l2 records """ network = member['network'] if network: if self.bigip_l2_manager.is_common_network(network): net_folder = 'Common' else: net_folder = pool['tenant_id'] fdb_info = { 'network': network, 'ip_address': member['address'], 'mac_address': member['port']['mac_address'] } self.bigip_l2_manager.add_bigip_fdbs(bigip, net_folder, fdb_info, member) def delete_bigip_member_l2(self, bigip, pool, member): """ Delete pool member l2 records """ network = member['network'] if network: if member['port']: if self.bigip_l2_manager.is_common_network(network): net_folder = 'Common' else: net_folder = pool['tenant_id'] fdb_info = { 'network': network, 'ip_address': member['address'], 'mac_address': member['port']['mac_address'] } self.bigip_l2_manager.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, vip): """ Update vip l2 records """ network = vip['network'] if network: if self.bigip_l2_manager.is_common_network(network): net_folder = 'Common' else: net_folder = vip['tenant_id'] fdb_info = { 'network': network, 'ip_address': None, 'mac_address': None } self.bigip_l2_manager.add_bigip_fdbs(bigip, net_folder, fdb_info, vip) def delete_bigip_vip_l2(self, bigip, vip): """ Delete vip l2 records """ network = vip['network'] if network: if self.bigip_l2_manager.is_common_network(network): net_folder = 'Common' else: net_folder = vip['tenant_id'] fdb_info = { 'network': network, 'ip_address': None, 'mac_address': None } self.bigip_l2_manager.delete_bigip_fdbs(bigip, net_folder, fdb_info, vip) def _assure_delete_nets_shared(self, bigip, service, subnet_hints): """ Assure shared configuration (which syncs) is deleted """ deleted_names = set() tenant_id = service['pool']['tenant_id'] delete_gateway = self.bigip_selfip_manager.delete_gateway_on_subnet for subnetinfo in _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 _get_subnets_to_delete(bigip, service, subnet_hints): try: network = subnetinfo['network'] if self.bigip_l2_manager.is_common_network(network): network_folder = 'Common' else: network_folder = service['pool']['tenant_id'] subnet = subnetinfo['subnet'] if self.conf.f5_populate_static_arp: bigip.arp.delete_by_subnet(subnet=subnet['cidr'], mask=None, folder=network_folder) local_selfip_name = "local-" + bigip.device_name + \ "-" + subnet['id'] selfip_address = bigip.selfip.get_addr(name=local_selfip_name, folder=network_folder) bigip.selfip.delete(name=local_selfip_name, folder=network_folder) if self.l3_binding: self.l3_binding.unbind_address(subnet_id=subnet['id'], ip_address=selfip_address) deleted_names.add(local_selfip_name) self.bigip_l2_manager.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['pool']['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
class NetworkBuilderDirect(object): """Create network connectivity for a bigip """ def __init__(self, conf, driver, bigip_l2_manager=None, l3_binding=None): self.conf = conf self.driver = driver self.bigip_l2_manager = bigip_l2_manager self.l3_binding = l3_binding self.bigip_selfip_manager = BigipSelfIpManager( driver, bigip_l2_manager, l3_binding) self.bigip_snat_manager = BigipSnatManager( driver, bigip_l2_manager, l3_binding) self.rds_cache = {} def initialize_tunneling(self): """ setup tunneling setup VTEP tunnels if needed """ 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 bigip.vxlan.create_multipoint_profile( name='vxlan_ovs', folder='Common') bigip.l2gre.create_multipoint_profile( name='gre_ovs', folder='Common') # find the IP address for the selfip for each box local_ip = bigip.selfip.get_addr(vtep_selfip_name, vtep_folder) if local_ip: bigip.local_ip = local_ip local_ips.append(local_ip) else: raise f5ex.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 for the service. """ if self.conf.f5_global_routed_mode or not service['pool']: return if self.conf.use_namespaces: self._annotate_service_route_domains(service) # Per Device Network Connectivity (VLANs or Tunnels) subnetsinfo = _get_subnets_to_assure(service) for (assure_bigip, subnetinfo) in \ itertools.product(self.driver.get_all_bigips(), subnetsinfo): self.bigip_l2_manager.assure_bigip_network( assure_bigip, subnetinfo['network']) self.bigip_selfip_manager.assure_bigip_selfip( assure_bigip, service, subnetinfo) # L3 Shared Config assure_bigips = self.driver.get_config_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: self._allocate_gw_addr(subnetinfo) 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['pool']['tenant_id'] self.update_rds_cache(tenant_id) if 'members' in service: for member in service['members']: LOG.debug("processing member %s" % member['address']) if 'address' in member: if 'network' in member and 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' in service and 'address' in service['vip']: vip = service['vip'] if 'network' in vip and vip['network']: self.assign_route_domain( tenant_id, vip['network'], vip['subnet']) rd_id = '%' + str(vip['network']['route_domain_id']) service['vip']['address'] += rd_id else: service['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.bigip_l2_manager.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) tenant_rd = bigip.route.get_domain(folder=tenant_id) network['route_domain_id'] = tenant_rd 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(): # folder = bigip.decorate_folder(tenant_id) bigip_id = bigip.route.create_domain( folder=tenant_id, strict_route_isolation=self.conf.f5_route_domain_strictness, is_aux=True) if route_domain_id is None: route_domain_id = bigip_id elif bigip_id != route_domain_id: LOG.debug( "Bigips allocated two different route domains!: %s %s" % (bigip_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 = bigip.route.get_domain_ids(folder=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 rd_vlans = bigip.route.get_vlans_in_domain_by_id( folder=tenant_id, route_domain_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: 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'] selfips = bigip.selfip.get_selfips(folder=tenant_id, vlan=rd_vlan) LOG.debug("rds_cache: got selfips: %s" % 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 = selfip['address'].split('/')[0] addr = addr.split('%')[0] netbits = selfip['address'].split('/')[1] 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 """ 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]: net_entry = tenant_cache[route_domain_id][net_short_name] if subnet['id'] in net_entry: del net_entry[subnet['id']] @staticmethod def get_bigip_net_short_name(bigip, tenant_id, network_name): """ Return <network_type>-<seg_id> for bigip network """ if '_tunnel-gre-' in network_name: tunnel_key = bigip.l2gre.get_tunnel_key( name=network_name, folder=tenant_id) return 'gre-%s' % tunnel_key elif '_tunnel-vxlan-' in network_name: tunnel_key = bigip.vxlan.get_tunnel_key( name=network_name, folder=tenant_id) return 'vxlan-%s' % tunnel_key else: vlan_id = bigip.vlan.get_id(name=network_name, folder=tenant_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['pool']['tenant_id'] subnet = subnetinfo['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]] if len(assure_bigips): snat_addrs = self.bigip_snat_manager.get_snat_addrs( subnetinfo, tenant_id) 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 """ network = subnetinfo['network'] if not network: LOG.error(_('Attempted to create default gateway' ' for network with no id.. skipping.')) return subnet = subnetinfo['subnet'] 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.error(_(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)) # avoids race condition: # deletion of shared ip objects must sync before we # remove the selfips or vlans from the peer bigips. self.driver.sync_if_clustered() # 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) if self.conf.f5_sync_mode == 'replication': subnet_hints = all_subnet_hints[bigip.device_name] else: # If in autosync mode, then the IP operations were performed # on just the primary big-ip, and so that is where the subnet # hints are stored. So, just use those hints for every bigip. device_name = self.driver.get_bigip().device_name subnet_hints = all_subnet_hints[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 """ vip = service['vip'] pool = service['pool'] for bigip in self.driver.get_all_bigips(): for member in service['members']: if member['status'] == plugin_const.PENDING_DELETE: self.delete_bigip_member_l2(bigip, pool, member) else: self.update_bigip_member_l2(bigip, pool, member) if 'id' in vip: if vip['status'] == plugin_const.PENDING_DELETE: self.delete_bigip_vip_l2(bigip, vip) else: self.update_bigip_vip_l2(bigip, vip) def update_bigip_member_l2(self, bigip, pool, member): """ update pool member l2 records """ network = member['network'] if network: if self.bigip_l2_manager.is_common_network(network): net_folder = 'Common' else: net_folder = pool['tenant_id'] fdb_info = {'network': network, 'ip_address': member['address'], 'mac_address': member['port']['mac_address']} self.bigip_l2_manager.add_bigip_fdbs( bigip, net_folder, fdb_info, member) def delete_bigip_member_l2(self, bigip, pool, member): """ Delete pool member l2 records """ network = member['network'] if network: if member['port']: if self.bigip_l2_manager.is_common_network(network): net_folder = 'Common' else: net_folder = pool['tenant_id'] fdb_info = {'network': network, 'ip_address': member['address'], 'mac_address': member['port']['mac_address']} self.bigip_l2_manager.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, vip): """ Update vip l2 records """ network = vip['network'] if network: if self.bigip_l2_manager.is_common_network(network): net_folder = 'Common' else: net_folder = vip['tenant_id'] fdb_info = {'network': network, 'ip_address': None, 'mac_address': None} self.bigip_l2_manager.add_bigip_fdbs( bigip, net_folder, fdb_info, vip) def delete_bigip_vip_l2(self, bigip, vip): """ Delete vip l2 records """ network = vip['network'] if network: if self.bigip_l2_manager.is_common_network(network): net_folder = 'Common' else: net_folder = vip['tenant_id'] fdb_info = {'network': network, 'ip_address': None, 'mac_address': None} self.bigip_l2_manager.delete_bigip_fdbs( bigip, net_folder, fdb_info, vip) def _assure_delete_nets_shared(self, bigip, service, subnet_hints): """ Assure shared configuration (which syncs) is deleted """ deleted_names = set() tenant_id = service['pool']['tenant_id'] delete_gateway = self.bigip_selfip_manager.delete_gateway_on_subnet for subnetinfo in _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 _get_subnets_to_delete(bigip, service, subnet_hints): try: network = subnetinfo['network'] if self.bigip_l2_manager.is_common_network(network): network_folder = 'Common' else: network_folder = service['pool']['tenant_id'] subnet = subnetinfo['subnet'] if self.conf.f5_populate_static_arp: bigip.arp.delete_by_subnet(subnet=subnet['cidr'], mask=None, folder=network_folder) local_selfip_name = "local-" + bigip.device_name + \ "-" + subnet['id'] selfip_address = bigip.selfip.get_addr(name=local_selfip_name, folder=network_folder) bigip.selfip.delete(name=local_selfip_name, folder=network_folder) if self.l3_binding: self.l3_binding.unbind_address(subnet_id=subnet['id'], ip_address=selfip_address) deleted_names.add(local_selfip_name) self.bigip_l2_manager.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['pool']['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