class PoolServiceBuilder(object): """Create LBaaS v2 pools and related objects on BIG-IP®s. Handles requests to create, update, delete LBaaS v2 pools, health monitors, and members on one or more BIG-IP® systems. """ def __init__(self, service_adapter): self.service_adapter = service_adapter self.http_mon_helper = BigIPResourceHelper(ResourceType.http_monitor) self.https_mon_helper = BigIPResourceHelper(ResourceType.https_monitor) self.tcp_mon_helper = BigIPResourceHelper(ResourceType.tcp_monitor) self.ping_mon_helper = BigIPResourceHelper(ResourceType.ping_monitor) self.pool_helper = BigIPResourceHelper(ResourceType.pool) self.node_helper = BigIPResourceHelper(ResourceType.node) def create_pool(self, service, bigips): """Create a pool on set of BIG-IP®s. Creates a BIG-IP® pool to represent an LBaaS pool object. :param service: Dictionary which contains a both a pool and load balancer definition. :param bigips: Array of BigIP class instances to create pool. """ pool = self.service_adapter.get_pool(service) for bigip in bigips: self.pool_helper.create(bigip, pool) def delete_pool(self, service, bigips): """Delete a pool on set of BIG-IP®s. Deletes a BIG-IP® pool defined by LBaaS pool object. :param service: Dictionary which contains a both a pool and load balancer definition. :param bigips: Array of BigIP class instances to delete pool. """ pool = self.service_adapter.get_pool(service) for bigip in bigips: self.pool_helper.delete(bigip, name=pool["name"], partition=pool["partition"]) def update_pool(self, service, bigips): """Update BIG-IP® pool. :param service: Dictionary which contains a both a pool and load balancer definition. :param bigips: Array of BigIP class instances to create pool. """ pool = self.service_adapter.get_pool(service) for bigip in bigips: self.pool_helper.update(bigip, pool) def create_healthmonitor(self, service, bigips): # create member hm = self.service_adapter.get_healthmonitor(service) hm_helper = self._get_monitor_helper(service) pool = self.service_adapter.get_pool(service) for bigip in bigips: hm_helper.create(bigip, hm) # update pool with new health monitor self.pool_helper.update(bigip, pool) def delete_healthmonitor(self, service, bigips): # delete health monitor hm = self.service_adapter.get_healthmonitor(service) hm_helper = self._get_monitor_helper(service) # update pool pool = self.service_adapter.get_pool(service) pool["monitor"] = "" for bigip in bigips: # need to first remove monitor reference from pool self.pool_helper.update(bigip, pool) # after updating pool, delete monitor hm_helper.delete(bigip, name=hm["name"], partition=hm["partition"]) def update_healthmonitor(self, service, bigips): hm = self.service_adapter.get_healthmonitor(service) hm_helper = self._get_monitor_helper(service) for bigip in bigips: hm_helper.update(bigip, hm) # Note: can't use BigIPResourceHelper class because members # are created within pool objects. Following member methods # use the F5® SDK directly. def create_member(self, service, bigips): pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) for bigip in bigips: part = pool["partition"] p = self.pool_helper.load(bigip, name=pool["name"], partition=part) m = p.members_s.members member_exists = m.exists(name=urllib.quote(member["name"]), partition=part) if not member_exists: m.create(**member) def delete_member(self, service, bigips): pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) part = pool["partition"] for bigip in bigips: p = self.pool_helper.load(bigip, name=pool["name"], partition=part) m = p.members_s.members member_exists = m.exists(name=urllib.quote(member["name"]), partition=part) if member_exists: m = m.load(name=urllib.quote(member["name"]), partition=part) m.delete() node = self.service_adapter.get_member_node(service) self.node_helper.delete(bigip, name=urllib.quote(node["name"]), partition=node["partition"]) def update_member(self, service, bigips): # TODO(jl) handle state -- SDK enforces at least state=None pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) part = pool["partition"] for bigip in bigips: p = self.pool_helper.load(bigip, name=pool["name"], partition=part) m = p.members_s.members if m.exists(name=urllib.quote(member["name"]), partition=part): m = m.load(name=urllib.quote(member["name"]), partition=part) m.modify(**member) def _get_monitor_helper(self, service): monitor_type = self.service_adapter.get_monitor_type(service) if monitor_type == "HTTPS": hm = self.https_mon_helper elif monitor_type == "TCP": hm = self.tcp_mon_helper elif monitor_type == "PING": hm = self.ping_mon_helper else: hm = self.http_mon_helper return hm
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': 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, snat_count): # Get the ip addresses for snat """ subnet = subnetinfo['subnet'] snat_addrs = [] snat_name = self._get_snat_name(subnet, tenant_id) for i in range(snat_count): ip_address = "" 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) if new_port is not None: ip_address = new_port['fixed_ips'][0]['ip_address'] # Push the IP address on the list if the port was acquired. if len(ip_address) > 0: snat_addrs.append(ip_address) else: LOG.error("get_snat_addrs: failed to allocate port for " "SNAT 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'] 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, snat_address in enumerate(snat_info['addrs']): ip_address = snat_address + \ '%' + str(network['route_domain_id']) index_snat_name = snat_name + "_" + str(i) 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 } try: if not self.snat_translation_manager.exists( bigip, name=index_snat_name, partition=snat_info['network_folder']): self.snat_translation_manager.create(bigip, model) except Exception as err: LOG.exception(err) raise f5_ex.SNATCreationException( "Error creating snat translation manager %s" % index_snat_name) model = { "name": snat_info['pool_name'], "partition": snat_info['network_folder'], } snatpool_member = ('/' + model["partition"] + '/' + index_snat_name) model["members"] = [snatpool_member] try: if not self.snatpool_manager.exists( bigip, name=model['name'], partition=model['partition']): LOG.debug("Creating SNAT pool: %s" % model) self.snatpool_manager.create(bigip, model) else: LOG.debug("Updating SNAT pool") snatpool = self.snatpool_manager.load( bigip, name=model["name"], partition=model["partition"] ) snatpool.members.append(snatpool_member) snatpool.modify(members=snatpool.members) except Exception as err: LOG.error("Create SNAT pool failed %s" % err.message) raise f5_ex.SNATCreationException( "Failed to create SNAT pool") 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 """ subnet = subnetinfo['subnet'] network = subnetinfo['network'] if self.l2_service.is_common_network(network): partition = 'Common' else: partition = self.driver.service_adapter.get_folder_name(tenant_id) snat_pool_name = 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) 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) except HTTPError as err: LOG.error("Load SNAT xlate failed %s" % err.message) except Exception: LOG.error("Unknown error occurred loading SNAT for unbind") else: self.l3_binding.unbind_address( subnet_id=subnet['id'], ip_address=snat_xlate.address) # 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, snat_pool_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.modify(members=snatpool.members) 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.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.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 def get_snatpool_member_use_count(self, bigip, member_name): snat_count = 0 snatpools = bigip.tm.ltm.snatpools.get_collection() for snatpool in snatpools: for member in snatpool.members: if member_name == os.path.basename(member): snat_count += 1 return snat_count
class PoolServiceBuilder(object): """Create LBaaS v2 pools and related objects on BIG-IPs. Handles requests to create, update, delete LBaaS v2 pools, health monitors, and members on one or more BIG-IP systems. """ def __init__(self, service_adapter): self.service_adapter = service_adapter self.http_mon_helper = BigIPResourceHelper(ResourceType.http_monitor) self.https_mon_helper = BigIPResourceHelper(ResourceType.https_monitor) self.tcp_mon_helper = BigIPResourceHelper(ResourceType.tcp_monitor) self.ping_mon_helper = BigIPResourceHelper(ResourceType.ping_monitor) self.pool_helper = BigIPResourceHelper(ResourceType.pool) self.node_helper = BigIPResourceHelper(ResourceType.node) def create_pool(self, service, bigips): """Create a pool on set of BIG-IPs. Creates a BIG-IP pool to represent an LBaaS pool object. :param service: Dictionary which contains a both a pool and load balancer definition. :param bigips: Array of BigIP class instances to create pool. """ pool = self.service_adapter.get_pool(service) for bigip in bigips: self.pool_helper.create(bigip, pool) def delete_pool(self, service, bigips): """Delete a pool on set of BIG-IPs. Deletes a BIG-IP pool defined by LBaaS pool object. :param service: Dictionary which contains a both a pool and load balancer definition. :param bigips: Array of BigIP class instances to delete pool. """ pool = self.service_adapter.get_pool(service) for bigip in bigips: self.pool_helper.delete(bigip, name=pool["name"], partition=pool["partition"]) def update_pool(self, service, bigips): """Update BIG-IP pool. :param service: Dictionary which contains a both a pool and load balancer definition. :param bigips: Array of BigIP class instances to create pool. """ pool = self.service_adapter.get_pool(service) for bigip in bigips: self.pool_helper.update(bigip, pool) def create_healthmonitor(self, service, bigips): # create member hm = self.service_adapter.get_healthmonitor(service) hm_helper = self._get_monitor_helper(service) pool = self.service_adapter.get_pool(service) for bigip in bigips: hm_helper.create(bigip, hm) # update pool with new health monitor self.pool_helper.update(bigip, pool) def delete_healthmonitor(self, service, bigips): # delete health monitor hm = self.service_adapter.get_healthmonitor(service) hm_helper = self._get_monitor_helper(service) # update pool pool = self.service_adapter.get_pool(service) pool["monitor"] = "" for bigip in bigips: # need to first remove monitor reference from pool self.pool_helper.update(bigip, pool) # after updating pool, delete monitor hm_helper.delete(bigip, name=hm["name"], partition=hm["partition"]) def update_healthmonitor(self, service, bigips): hm = self.service_adapter.get_healthmonitor(service) hm_helper = self._get_monitor_helper(service) pool = self.service_adapter.get_pool(service) for bigip in bigips: hm_helper.update(bigip, hm) # update pool with new health monitor self.pool_helper.update(bigip, pool) # Note: can't use BigIPResourceHelper class because members # are created within pool objects. Following member methods # use the F5 SDK directly. def create_member(self, service, bigips): pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) for bigip in bigips: part = pool["partition"] p = self.pool_helper.load(bigip, name=pool["name"], partition=part) m = p.members_s.members m.create(**member) def delete_member(self, service, bigips): pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) part = pool["partition"] for bigip in bigips: p = self.pool_helper.load(bigip, name=pool["name"], partition=part) m = p.members_s.members member_exists = m.exists(name=urllib.quote(member["name"]), partition=part) if member_exists: m = m.load(name=urllib.quote(member["name"]), partition=part) m.delete() try: node = self.service_adapter.get_member_node(service) self.node_helper.delete(bigip, name=urllib.quote(node["name"]), partition=node["partition"]) except HTTPError as err: # Possilbe error if node is shared with another member. # If so, ignore the error. if err.response.status_code == 400: LOG.debug(err.message) else: raise def update_member(self, service, bigips): pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) part = pool["partition"] for bigip in bigips: p = self.pool_helper.load(bigip, name=pool["name"], partition=part) m = p.members_s.members if m.exists(name=urllib.quote(member["name"]), partition=part): m = m.load(name=urllib.quote(member["name"]), partition=part) member.pop("address", None) m.modify(**member) def delete_orphaned_members(self, service, bigips): pool = self.service_adapter.get_pool(service) srv_members = service['members'] part = pool['partition'] for bigip in bigips: p = self.pool_helper.load(bigip, name=pool['name'], partition=part) deployed_members = p.members_s.get_collection() for dm in deployed_members: orphaned = True for sm in srv_members: svc = { "loadbalancer": service["loadbalancer"], "pool": service["pool"], "member": sm } member = self.service_adapter.get_member(svc) if member['name'] == dm.name: orphaned = False if orphaned: node_name = dm.address dm.delete() try: self.node_helper.delete(bigip, name=urllib.quote(node_name), partition=part) except HTTPError as err: # Possilbe error if node is shared with another member. # If so, ignore the error. if err.response.status_code == 400: LOG.debug(err.message) else: raise def _get_monitor_helper(self, service): monitor_type = self.service_adapter.get_monitor_type(service) if monitor_type == "HTTPS": hm = self.https_mon_helper elif monitor_type == "TCP": hm = self.tcp_mon_helper elif monitor_type == "PING": hm = self.ping_mon_helper else: hm = self.http_mon_helper return hm def get_member_status(self, service, bigip, status_keys): """Return status values for a single pool. Status keys to collect are defined as an array of strings in input status_keys. :param service: Has pool and member name/partition :param bigip: BIG-IP to get member status from. :param status_keys: Array of strings that define which status keys to collect. :return: A dict with key/value pairs for each status defined in input status_keys. """ member_status = {} pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) part = pool["partition"] try: p = self.pool_helper.load(bigip, name=pool["name"], partition=part) m = p.members_s.members if m.exists(name=urllib.quote(member["name"]), partition=part): m = m.load(name=urllib.quote(member["name"]), partition=part) member_status = self.pool_helper.collect_stats( m, stat_keys=status_keys) else: LOG.error( "Unable to get member status. " "Member %s does not exist.", member["name"]) except Exception as e: # log error but continue on LOG.error("Error getting member status: %s", e.message) return member_status
class PoolServiceBuilder(object): """Create LBaaS v2 pools and related objects on BIG-IPs. Handles requests to create, update, delete LBaaS v2 pools, health monitors, and members on one or more BIG-IP systems. """ def __init__(self, service_adapter): self.service_adapter = service_adapter self.http_mon_helper = BigIPResourceHelper(ResourceType.http_monitor) self.https_mon_helper = BigIPResourceHelper(ResourceType.https_monitor) self.tcp_mon_helper = BigIPResourceHelper(ResourceType.tcp_monitor) self.ping_mon_helper = BigIPResourceHelper(ResourceType.ping_monitor) self.pool_helper = BigIPResourceHelper(ResourceType.pool) self.node_helper = BigIPResourceHelper(ResourceType.node) def create_pool(self, service, bigips): """Create a pool on set of BIG-IPs. Creates a BIG-IP pool to represent an LBaaS pool object. :param service: Dictionary which contains a both a pool and load balancer definition. :param bigips: Array of BigIP class instances to create pool. """ pool = self.service_adapter.get_pool(service) for bigip in bigips: self.pool_helper.create(bigip, pool) def delete_pool(self, service, bigips): """Delete a pool on set of BIG-IPs. Deletes a BIG-IP pool defined by LBaaS pool object. :param service: Dictionary which contains a both a pool and load balancer definition. :param bigips: Array of BigIP class instances to delete pool. """ pool = self.service_adapter.get_pool(service) for bigip in bigips: self.pool_helper.delete(bigip, name=pool["name"], partition=pool["partition"]) def update_pool(self, service, bigips): """Update BIG-IP pool. :param service: Dictionary which contains a both a pool and load balancer definition. :param bigips: Array of BigIP class instances to create pool. """ pool = self.service_adapter.get_pool(service) for bigip in bigips: self.pool_helper.update(bigip, pool) def create_healthmonitor(self, service, bigips): # create member hm = self.service_adapter.get_healthmonitor(service) hm_helper = self._get_monitor_helper(service) pool = self.service_adapter.get_pool(service) for bigip in bigips: hm_helper.create(bigip, hm) # update pool with new health monitor self.pool_helper.update(bigip, pool) def delete_healthmonitor(self, service, bigips): # delete health monitor hm = self.service_adapter.get_healthmonitor(service) hm_helper = self._get_monitor_helper(service) # update pool pool = self.service_adapter.get_pool(service) pool["monitor"] = "" for bigip in bigips: # need to first remove monitor reference from pool self.pool_helper.update(bigip, pool) # after updating pool, delete monitor hm_helper.delete(bigip, name=hm["name"], partition=hm["partition"]) def update_healthmonitor(self, service, bigips): hm = self.service_adapter.get_healthmonitor(service) hm_helper = self._get_monitor_helper(service) pool = self.service_adapter.get_pool(service) for bigip in bigips: hm_helper.update(bigip, hm) # update pool with new health monitor self.pool_helper.update(bigip, pool) # Note: can't use BigIPResourceHelper class because members # are created within pool objects. Following member methods # use the F5 SDK directly. def create_member(self, service, bigips): pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) for bigip in bigips: part = pool["partition"] p = self.pool_helper.load(bigip, name=pool["name"], partition=part) m = p.members_s.members m.create(**member) def delete_member(self, service, bigips): pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) part = pool["partition"] for bigip in bigips: p = self.pool_helper.load(bigip, name=pool["name"], partition=part) m = p.members_s.members member_exists = m.exists(name=urllib.quote(member["name"]), partition=part) if member_exists: m = m.load(name=urllib.quote(member["name"]), partition=part) m.delete() try: node = self.service_adapter.get_member_node(service) self.node_helper.delete(bigip, name=urllib.quote(node["name"]), partition=node["partition"]) except HTTPError as err: # Possilbe error if node is shared with another member. # If so, ignore the error. if err.response.status_code == 400: LOG.debug(err.message) else: raise def update_member(self, service, bigips): pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) part = pool["partition"] for bigip in bigips: p = self.pool_helper.load(bigip, name=pool["name"], partition=part) m = p.members_s.members if m.exists(name=urllib.quote(member["name"]), partition=part): m = m.load(name=urllib.quote(member["name"]), partition=part) member.pop("address", None) m.modify(**member) def delete_orphaned_members(self, service, bigips): pool = self.service_adapter.get_pool(service) srv_members = service['members'] part = pool['partition'] for bigip in bigips: p = self.pool_helper.load(bigip, name=pool['name'], partition=part) deployed_members = p.members_s.get_collection() for dm in deployed_members: orphaned = True for sm in srv_members: svc = {"loadbalancer": service["loadbalancer"], "pool": service["pool"], "member": sm} member = self.service_adapter.get_member(svc) if member['name'] == dm.name: orphaned = False if orphaned: node_name = dm.address dm.delete() try: self.node_helper.delete(bigip, name=urllib.quote(node_name), partition=part) except HTTPError as err: # Possilbe error if node is shared with another member. # If so, ignore the error. if err.response.status_code == 400: LOG.debug(err.message) else: raise def _get_monitor_helper(self, service): monitor_type = self.service_adapter.get_monitor_type(service) if monitor_type == "HTTPS": hm = self.https_mon_helper elif monitor_type == "TCP": hm = self.tcp_mon_helper elif monitor_type == "PING": hm = self.ping_mon_helper else: hm = self.http_mon_helper return hm def get_member_status(self, service, bigip, status_keys): """Return status values for a single pool. Status keys to collect are defined as an array of strings in input status_keys. :param service: Has pool and member name/partition :param bigip: BIG-IP to get member status from. :param status_keys: Array of strings that define which status keys to collect. :return: A dict with key/value pairs for each status defined in input status_keys. """ member_status = {} pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) part = pool["partition"] try: p = self.pool_helper.load(bigip, name=pool["name"], partition=part) m = p.members_s.members if m.exists(name=urllib.quote(member["name"]), partition=part): m = m.load(name=urllib.quote(member["name"]), partition=part) member_status = self.pool_helper.collect_stats( m, stat_keys=status_keys) else: LOG.error("Unable to get member status. " "Member %s does not exist.", member["name"]) except Exception as e: # log error but continue on LOG.error("Error getting member status: %s", e.message) return member_status
class PoolServiceBuilder(object): """Create LBaaS v2 pools and related objects on BIG-IPs. Handles requests to create, update, delete LBaaS v2 pools, health monitors, and members on one or more BIG-IP systems. """ def __init__(self, service_adapter): self.service_adapter = service_adapter self.http_mon_helper = BigIPResourceHelper(ResourceType.http_monitor) self.https_mon_helper = BigIPResourceHelper(ResourceType.https_monitor) self.tcp_mon_helper = BigIPResourceHelper(ResourceType.tcp_monitor) self.ping_mon_helper = BigIPResourceHelper(ResourceType.ping_monitor) self.pool_helper = BigIPResourceHelper(ResourceType.pool) self.node_helper = BigIPResourceHelper(ResourceType.node) def create_pool(self, service, bigips): """Create a pool on set of BIG-IPs. Creates a BIG-IP pool to represent an LBaaS pool object. :param service: Dictionary which contains a both a pool and load balancer definition. :param bigips: Array of BigIP class instances to create Listener. """ pool = self.service_adapter.get_pool(service) for bigip in bigips: try: self.pool_helper.create(bigip, pool) except HTTPError as err: LOG.error("Error creating pool %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (pool["name"], bigip.device_name, err.response.status_code, err.message)) def delete_pool(self, service, bigips): """Delete a pool on set of BIG-IPs. Deletes a BIG-IP pool defined by LBaaS pool object. :param service: Dictionary which contains a both a pool and load balancer definition. :param bigips: Array of BigIP class instances to delete pool. """ pool = self.service_adapter.get_pool(service) for bigip in bigips: try: self.pool_helper.delete(bigip, name=pool["name"], partition=pool["partition"]) except HTTPError as err: LOG.error("Error deleting pool %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (pool["name"], bigip.device_name, err.response.status_code, err.message)) def update_pool(self, service, bigips): """Update BIG-IP pool. :param service: Dictionary which contains a both a pool and load balancer definition. :param bigip: Array of BigIP class instances to create Listener. """ pool = self.service_adapter.get_pool(service) for bigip in bigips: try: self.pool_helper.update(bigip, pool) except HTTPError as err: LOG.error("Error updating pool %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (pool["name"], bigip.device_name, err.response.status_code, err.message)) def create_healthmonitor(self, service, bigips): # create member hm = self.service_adapter.get_healthmonitor(service) hm_helper = self._get_monitor_helper(service) for bigip in bigips: try: hm_helper.create(bigip, hm) except HTTPError as err: LOG.error("Error creating health monitor %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (hm["name"], bigip.device_name, err.response.status_code, err.message)) # update pool with new health monitor pool = self.service_adapter.get_pool(service) for bigip in bigips: self.pool_helper.update(bigip, pool) def delete_healthmonitor(self, service, bigips): # delete health monitor hm = self.service_adapter.get_healthmonitor(service) hm_helper = self._get_monitor_helper(service) # update pool pool = self.service_adapter.get_pool(service) pool["monitor"] = "" for bigip in bigips: # need to first remove monitor reference from pool try: self.pool_helper.update(bigip, pool) except HTTPError as err: LOG.error("Error updating pool %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (pool["name"], bigip.device_name, err.response.status_code, err.message)) try: hm_helper.delete(bigip, name=hm["name"], partition=hm["partition"]) except HTTPError as err: LOG.error("Error deleting health monitor %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (hm["name"], bigip.device_name, err.response.status_code, err.message)) def update_healthmonitor(self, service, bigips): hm = self.service_adapter.get_healthmonitor(service) hm_helper = self._get_monitor_helper(service) for bigip in bigips: try: hm_helper.delete(bigip, name=hm["name"], partition=hm["partition"]) except HTTPError as err: LOG.error("Error updating health monitor %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (hm["name"], bigip.device_name, err.response.status_code, err.message)) # Note: can't use BigIPResourceHelper class because members # are created within pool objects. Following member methods # use the F5 SDK directly. def create_member(self, service, bigips): pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) for bigip in bigips: part = pool["partition"] try: p = self.pool_helper.load(bigip, name=pool["name"], partition=part) except HTTPError as err: LOG.error("Error loading pool %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (pool["name"], bigip.device_name, err.response.status_code, err.message)) continue m = p.members_s.members try: member_exists = m.exists(name=member["name"], partition=part) except HTTPError as err: LOG.error("Error checking if member %s exists on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (member["name"], bigip.device_name, err.response.status_code, err.message)) continue if not member_exists: try: m.create(**member) except HTTPError as err: LOG.error("Error creating member %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (member["name"], bigip.device_name, err.response.status_code, err.message)) def delete_member(self, service, bigips): pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) part = pool["partition"] for bigip in bigips: try: p = self.pool_helper.load(bigip, name=pool["name"], partition=part) except HTTPError as err: LOG.error("Error loading pool %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (pool["name"], bigip.device_name, err.response.status_code, err.message)) continue m = p.members_s.members try: member_exists = m.exists(name=member["name"], partition=part) except HTTPError as err: LOG.error("Error checking if member %s exists on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (member["name"], bigip.device_name, err.response.status_code, err.message)) continue if member_exists: try: m = m.load(name=member["name"], partition=part) except HTTPError as err: LOG.error("Error loading member %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (member["name"], bigip.device_name, err.response.status_code, err.message)) continue try: m.delete() node = self.service_adapter.get_member_node(service) self.node_helper.delete(bigip, name=node["name"], partition=node["partition"]) except HTTPError as err: LOG.error("Error deleting member %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (member["name"], bigip.device_name, err.response.status_code, err.message)) def update_member(self, service, bigips): # TODO(jl) handle state -- SDK enforces at least state=None pool = self.service_adapter.get_pool(service) member = self.service_adapter.get_member(service) part = pool["partition"] for bigip in bigips: try: p = self.pool_helper.load(bigip, name=pool["name"], partition=part) except HTTPError as err: LOG.error("Error loading pool %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (pool["name"], bigip.device_name, err.response.status_code, err.message)) continue m = p.members_s.members try: member_exists = m.exists(name=member["name"], partition=part) except HTTPError as err: LOG.error("Error checking if member %s exists on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (member["name"], bigip.device_name, err.response.status_code, err.message)) continue if member_exists: try: m = m.load(name=member["name"], partition=part) except HTTPError as err: LOG.error("Error loading member %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (member["name"], bigip.device_name, err.response.status_code, err.message)) continue try: m.update(**member) except HTTPError as err: LOG.error("Error updating member %s on BIG-IP %s. " "Repsponse status code: %s. Response " "message: %s." % (member["name"], bigip.device_name, err.response.status_code, err.message)) def _get_monitor_helper(self, service): monitor_type = self.service_adapter.get_monitor_type(service) if monitor_type == "HTTPS": hm = self.https_mon_helper elif monitor_type == "TCP": hm = self.tcp_mon_helper elif monitor_type == "PING": hm = self.ping_mon_helper else: hm = self.http_mon_helper return hm