def test_f5label(self):
        self.assertEqual(f5label("test test test"), "test test test")
        self.assertEqual(f5label("[dev]"), "dev")
        self.assertEqual(len(f5label(65 * "A")), 64)

        FORBIDDEN_CHARACTERS = "#&*<>?[\\]`\""
        for forbidden_char in FORBIDDEN_CHARACTERS:
            self.assertEqual(f5label(forbidden_char), "")
    def test_f5remark(self):
        self.assertEqual(f5remark("test test test"), "test test test")
        self.assertEqual(f5remark("\Programm Files"), "Programm Files")
        self.assertEqual(len(f5remark(65 * "A")), 64)

        FORBIDDEN_CHARACTERS = "\\\""
        for forbidden_char in FORBIDDEN_CHARACTERS:
            self.assertEqual(f5label(forbidden_char), "")
Example #3
0
def get_pool(pool, loadbalancer_ips, status):
    """Map Octavia Pool -> AS3 Pool object

    :param pool: octavia pool object
    :param loadbalancer_ips: already used loadbalancer_ips
    :param status: status manager instance
    :return: AS3 pool
    """

    # Entities is a list of tuples, which each describe AS3 objects
    # which may reference each other but do not form a hierarchy.
    entities = []
    lbaas_lb_method = pool.lb_algorithm.upper()
    lbmode = _set_lb_method(lbaas_lb_method, pool.members)

    service_args = {
        'label': as3types.f5label(pool.name or pool.description),
        'remark': as3types.f5remark(pool.description or pool.name),
        'loadBalancingMode': lbmode,
        'members': [],
    }

    enable_priority_group = any([member.backup for member in pool.members])
    for member in pool.members:
        if not utils.pending_delete(member):
            if member.ip_address in loadbalancer_ips:
                LOG.warning("The member address %s of member %s (pool %s, LB %s) is already in use by another load balancer.",
                            member.ip_address, member.id, member.pool.id, member.pool.load_balancer.id)
                if status:
                    status.set_error(member)
                continue

            if member.ip_address == '0.0.0.0':
                LOG.warning("The member address 0.0.0.0 of member %s is prohibited.", member.id)
                if status:
                    status.set_error(member)
                continue

            service_args['members'].append(
                m_member.get_member(member, enable_priority_group, pool.health_monitor))

            # add custom member monitors
            if pool.health_monitor and (member.monitor_address or member.monitor_port):
                member_hm = m_monitor.get_monitor(pool.health_monitor,
                                                  member.monitor_address or member.ip_address,
                                                  member.monitor_port or member.protocol_port)
                entities.append((m_monitor.get_name(member.id), member_hm))

    if pool.health_monitor and not utils.pending_delete(
            pool.health_monitor):
        monitor_name = m_monitor.get_name(pool.health_monitor.id)
        entities.append((monitor_name, m_monitor.get_monitor(pool.health_monitor)))
        service_args['monitors'] = [Pointer(use=monitor_name)]

    entities.append((get_name(pool.id), Pool(**service_args)))
    return entities
def get_endpoint_policy(l7policies):
    wrapper_name = ', '.join(
        [l7policy.name for l7policy in l7policies if l7policy.name])
    wrapper_desc = ', '.join([
        l7policy.description for l7policy in l7policies if l7policy.description
    ])

    args = dict()
    args['label'] = as3types.f5label(wrapper_name or wrapper_desc)
    args['remark'] = as3types.f5remark(wrapper_desc or wrapper_name)
    args['rules'] = [
        Endpoint_Policy_Rule(
            name=get_name(l7policy.id),
            label=as3types.f5label(l7policy.name or l7policy.description),
            remark=as3types.f5remark(l7policy.description or l7policy.name),
            conditions=[_get_condition(l7rule) for l7rule in l7policy.l7rules],
            actions=[_get_action(l7policy)]) for l7policy in l7policies
    ]
    args['strategy'] = 'first-match'
    return Endpoint_Policy(**args)
Example #5
0
def get_ca_bundle(bundle, remark='', label=''):
    """AS3 Certificate Authority Bundle object.

    :param bundle: the CA certificate bundle as PEM encoded bytes
    :param remark: comment
    :param label: label
    :return: AS3 CA_Bundle
    """
    service_args = {
        'remark': as3types.f5remark(remark),
        'label': as3types.f5label(label),
        'bundle': bundle.decode('utf-8').replace('\r',  '')
    }
    return CA_Bundle(**service_args)
def get_pool(pool):
    """Map Octavia Pool -> AS3 Pool object

    :param pool: octavia pool object
    :return: AS3 pool
    """

    # Entities is a list of tuples, which each describe AS3 objects
    # which may reference each other but do not form a hierarchy.
    entities = []
    lbaas_lb_method = pool.lb_algorithm.upper()
    lbmode = _set_lb_method(lbaas_lb_method, pool.members)

    service_args = {
        'label': as3types.f5label(pool.name or pool.id),
        'remark': as3types.f5remark(pool.description or pool.id),
        'loadBalancingMode': lbmode,
        'members': [],
    }

    for member in pool.members:
        # Ignore backup members, will be handled by service
        if not utils.pending_delete(member) and not member.backup:
            service_args['members'].append(m_member.get_member(member))

    #if pool.health_monitor and not utils.pending_delete(
    #        pool.health_monitor):
    # Workaround for Monitor deletion bug in AS3, dereference but remain HM
    if pool.health_monitor:
        hms = m_monitor.get_monitors(pool.health_monitor, pool.members)
        entities.extend(hms)

        # Part of the workaround
        if not utils.pending_delete(pool.health_monitor):
            service_args['monitors'] = [Pointer(use=name) for name, _ in hms]

    entities.append((get_name(pool.id), Pool(**service_args)))
    return entities
Example #7
0
def get_service(listener, cert_manager, esd_repository):
    """ Map Octavia listener -> AS3 Service

    :param listener: Octavia listener
    :param cert_manager: cert_manager wrapper instance
    :return: AS3 Service + additional AS3 application objects
    """

    # Entities is a list of tuples, which each describe AS3 objects
    # which may reference each other but do not form a hierarchy.
    entities = []
    vip = listener.load_balancer.vip
    project_id = listener.load_balancer.project_id
    label = as3types.f5label(listener.name or listener.description)
    virtual_address = '{}/32'.format(vip.ip_address)
    service_args = {
        'virtualPort': listener.protocol_port,
        'persistenceMethods': [],
        'iRules': [],
        'policyEndpoint': [],
        'label': label
    }

    # Custom virtual address settings
    if CONF.f5_agent.service_address_icmp_echo:
        service_address = as3.ServiceAddress(
            virtualAddress=virtual_address,
            icmpEcho=CONF.f5_agent.service_address_icmp_echo)
        entities.append(
            (m_app.get_name(listener.load_balancer.id), service_address))
        service_args['virtualAddresses'] = [[
            as3.Pointer(m_app.get_name(listener.load_balancer.id)),
            virtual_address
        ]]
    else:
        service_args['virtualAddresses'] = [virtual_address]

    # Determine service type
    if listener.protocol == const.PROTOCOL_TCP:
        service_args['_servicetype'] = CONF.f5_agent.tcp_service_type
    # UDP
    elif listener.protocol == const.PROTOCOL_UDP:
        service_args['_servicetype'] = const.SERVICE_UDP
    # HTTP
    elif listener.protocol == const.PROTOCOL_HTTP:
        service_args['_servicetype'] = const.SERVICE_HTTP
    # HTTPS (non-terminated, forward TCP traffic)
    elif listener.protocol == const.PROTOCOL_HTTPS:
        service_args['_servicetype'] = CONF.f5_agent.tcp_service_type
    # Proxy
    elif listener.protocol == const.PROTOCOL_PROXY:
        service_args['_servicetype'] = const.SERVICE_TCP
        name, irule = m_irule.get_proxy_irule()
        service_args['iRules'].append(name)
        entities.append((name, irule))
    # Terminated HTTPS
    elif listener.protocol == const.PROTOCOL_TERMINATED_HTTPS:
        service_args['_servicetype'] = const.SERVICE_HTTPS
        service_args['serverTLS'] = m_tls.get_listener_name(listener.id)
        service_args['redirect80'] = False

        # Certificate Handling
        auth_name = None
        certificates = cert_manager.get_certificates(listener)
        if listener.client_ca_tls_certificate_id and listener.client_authentication != 'NONE':
            # Client Side Certificates
            try:
                auth_name, secret = cert_manager.load_secret(
                    project_id, listener.client_ca_tls_certificate_id)
                entities.append((auth_name,
                                 m_cert.get_ca_bundle(secret, auth_name,
                                                      auth_name)))
            except exceptions.CertificateRetrievalException as e:
                LOG.error("Error fetching certificate: %s", e)

        entities.append(
            (m_tls.get_listener_name(listener.id),
             m_tls.get_tls_server([cert['id'] for cert in certificates],
                                  auth_name, listener.client_authentication)))
        entities.extend([(cert['id'], cert['as3']) for cert in certificates])

    if listener.connection_limit > 0:
        service_args['maxConnections'] = listener.connection_limit

    # Add default pool
    if listener.default_pool_id:
        pool = listener.default_pool
        if pool.provisioning_status != lib_consts.PENDING_DELETE:
            default_pool = m_pool.get_name(listener.default_pool_id)
            service_args['pool'] = default_pool

            # only consider Proxy pool, everything else is determined by listener type
            if pool.protocol == const.PROTOCOL_PROXY:
                name, irule = m_irule.get_proxy_irule()
                service_args['iRules'].append(name)
                entities.append((name, irule))

        # Pool member certificate handling (TLS backends)
        if pool.tls_enabled and listener.protocol in \
                [ const.PROTOCOL_PROXY, const.PROTOCOL_HTTP, const.PROTOCOL_TERMINATED_HTTPS ]:
            client_cert = None
            trust_ca = None
            crl_file = None

            service_args['clientTLS'] = m_tls.get_pool_name(pool.id)
            certificates = cert_manager.get_certificates(pool)
            if len(certificates) == 1:
                cert = certificates.pop()
                entities.append((cert['id'], cert['as3']))
                client_cert = cert['id']

            if pool.ca_tls_certificate_id:
                trust_ca, secret = cert_manager.load_secret(
                    project_id, pool.ca_tls_certificate_id)
                entities.append(
                    (trust_ca, m_cert.get_ca_bundle(secret, trust_ca,
                                                    trust_ca)))

            if pool.crl_container_id:
                # TODO: CRL currently not supported
                pass

            entities.append((m_tls.get_pool_name(pool.id),
                             m_tls.get_tls_client(trust_ca=trust_ca,
                                                  client_cert=client_cert,
                                                  crl_file=crl_file)))

    # Insert header irules
    if service_args['_servicetype'] in const.SERVICE_HTTP_TYPES:
        # HTTP profiles only
        for name, irule in m_irule.get_header_irules(listener.insert_headers):
            service_args['iRules'].append(name)
            entities.append((name, irule))

    # session persistence
    if listener.default_pool_id and listener.default_pool.session_persistence:
        persistence = listener.default_pool.session_persistence
        lb_algorithm = listener.default_pool.lb_algorithm

        if service_args['_servicetype'] in const.SERVICE_HTTP_TYPES:
            # Add APP_COOKIE / HTTP_COOKIE persistance only in HTTP profiles
            if persistence.type == 'APP_COOKIE' and persistence.cookie_name:
                # generate iRule for cookie_name
                escaped_cookie = persistence.cookie_name
                escaped_cookie.replace("\"", "")
                irule_name, irule = m_irule.get_app_cookie_irule(
                    escaped_cookie)
                entities.append((irule_name, irule))

                # add iRule to universal persistance profile
                name, obj_persist = m_persist.get_app_cookie(escaped_cookie)
                service_args['persistenceMethods'] = [as3.Pointer(name)]
                entities.append((name, obj_persist))
                if lb_algorithm == 'SOURCE_IP':
                    service_args[
                        'fallbackPersistenceMethod'] = 'source-address'

            elif persistence.type == 'HTTP_COOKIE':
                service_args['persistenceMethods'] = ['cookie']
                if lb_algorithm == 'SOURCE_IP':
                    service_args[
                        'fallbackPersistenceMethod'] = 'source-address'

        if persistence.type == 'SOURCE_IP':
            if not persistence.persistence_timeout and not persistence.persistence_granularity:
                service_args['persistenceMethods'] = ['source-address']
            else:
                name, obj_persist = m_persist.get_source_ip(
                    persistence.persistence_timeout,
                    persistence.persistence_granularity)
                service_args['persistenceMethods'] = [as3.Pointer(name)]
                entities.append((name, obj_persist))

    # Map listener tags to ESDs
    for tag in listener.tags:

        # get ESD of same name
        esd = esd_repository.get_esd(tag)
        if esd is None:
            continue

        # enrich service with iRules and other things defined in ESD
        esd_entities = get_esd_entities(service_args['_servicetype'], esd)
        for entity_name in esd_entities:
            if entity_name == 'iRules':
                service_args['iRules'].extend(esd_entities['iRules'])
            else:
                service_args[entity_name] = esd_entities[entity_name]

    endpoint_policies = []
    # Map special L7policies to ESDs
    # TODO: Remove this as soon as all customers have migrated their scripts.
    # Triggering ESDs via L7policies is considered deprecated. Tags should be used instead. See the code above.
    for policy in listener.l7policies:
        # get ESD of same name
        esd = esd_repository.get_esd(policy.name)

        # Add ESD or regular endpoint policy
        if esd:
            # enrich service with iRules and other things defined in ESD
            esd_entities = get_esd_entities(service_args['_servicetype'], esd)
            for entity_name in esd_entities:
                if entity_name == 'iRules':
                    service_args['iRules'].extend(esd_entities['iRules'])
                else:
                    service_args[entity_name] = esd_entities[entity_name]
        elif policy.provisioning_status != lib_consts.PENDING_DELETE:
            endpoint_policies.append(policy)

    # UDP listener won't support policies
    if endpoint_policies and not service_args[
            '_servicetype'] == const.SERVICE_UDP:
        # add a regular endpoint policy
        policy_name = m_policy.get_wrapper_name(listener.id)

        # make endpoint policy object
        endpoint_policy = (policy_name,
                           m_policy.get_endpoint_policy(endpoint_policies))
        entities.append(endpoint_policy)

        # reference endpoint policy object in service
        service_args['policyEndpoint'].append(policy_name)

    # Ensure no duplicate iRules
    service_args['iRules'] = list(set(service_args['iRules']))

    # fastL4 profile doesn't support iRules, fallback to TCP Profile when iRules detected
    if service_args['_servicetype'] == const.SERVICE_L4 and len(
            service_args['iRules']) > 0:
        service_args['_servicetype'] = const.SERVICE_TCP

    # add default profiles to supported listeners
    if CONF.f5_agent.profile_http and service_args[
            '_servicetype'] in const.SERVICE_HTTP_TYPES:
        if 'profileHTTP' not in service_args:
            service_args['profileHTTP'] = as3.BigIP(CONF.f5_agent.profile_http)
    if CONF.f5_agent.profile_l4 and service_args[
            '_servicetype'] == const.SERVICE_L4:
        if 'profileL4' not in service_args:
            service_args['profileL4'] = as3.BigIP(CONF.f5_agent.profile_l4)
    if CONF.f5_agent.profile_tcp and service_args[
            '_servicetype'] in const.SERVICE_TCP_TYPES:
        if 'profileTCP' not in service_args:
            service_args['profileTCP'] = as3.BigIP(CONF.f5_agent.profile_tcp)
    if CONF.f5_agent.profile_udp and service_args[
            '_servicetype'] == const.SERVICE_UDP:
        if 'profileUDP' not in service_args:
            service_args['profileUDP'] = as3.BigIP(CONF.f5_agent.profile_udp)

    # Use the virtual-server address as SNAT address
    if CONF.f5_agent.snat_virtual:
        service_args['snat'] = 'self'

    # create service object and fill in additional fields
    service = as3.Service(**service_args)

    # add service to entities and return
    entities.append((get_name(listener.id), service))
    return entities
def get_monitor(health_monitor, target_address=None, target_port=None):
    args = dict()

    # Standard Octavia monitor types
    if health_monitor.type == 'HTTP':
        args['monitorType'] = 'http'
    elif health_monitor.type == 'HTTPS':
        args['monitorType'] = 'https'
    elif health_monitor.type == 'PING':
        args['monitorType'] = 'icmp'
    elif health_monitor.type == 'TCP':
        args['monitorType'] = 'tcp'
        args['send'] = ''
        args['receive'] = ''
    elif health_monitor.type == 'TLS-HELLO':
        args['monitorType'] = 'external'
        args['script'] = TLS_HELLO_CHECK
        args['receive'] = 'UP'
    elif health_monitor.type == 'UDP-CONNECT':
        args['monitorType'] = 'external'
        args['script'] = UDP_CHECK
        args['receive'] = 'UP'

    # F5 specific monitory types
    elif health_monitor.type == 'SIP':
        args['monitorType'] = 'sip'
    elif health_monitor.type == 'SMTP':
        args['monitorType'] = 'smtp'
    elif health_monitor.type == 'TCP-HALF_OPEN':
        args['monitorType'] = 'tcp-half-open'
    elif health_monitor.type == 'LDAP':
        args['monitorType'] = 'ldap'
    elif health_monitor.type == 'DNS':
        args['monitorType'] = 'dns'
        args['queryName'] = health_monitor.domain_name
    # No Health monitor type available
    else:
        return {}

    if health_monitor.type == 'HTTP' or health_monitor.type == 'HTTPS':
        http_version = '1.0'
        if health_monitor.http_version:
            http_version = health_monitor.http_version
        send = "{} {} HTTP/{}\\r\\n".format(health_monitor.http_method,
                                            health_monitor.url_path,
                                            http_version)
        if health_monitor.domain_name:
            send += "Host: {}\\r\\n\\r\\n".format(health_monitor.domain_name)
        else:
            send += "\\r\\n"

        args['send'] = send
        args['receive'] = _get_recv_text(health_monitor)

    if health_monitor.delay:
        args["interval"] = health_monitor.delay
    if health_monitor.timeout:
        timeout = (int(health_monitor.fall_threshold) *
                   int(health_monitor.timeout))
        args["timeout"] = timeout
    if health_monitor.rise_threshold:
        time_until_up = (int(health_monitor.rise_threshold) *
                         int(health_monitor.timeout))
        args["timeUntilUp"] = time_until_up
    if target_address:
        args["targetAddress"] = target_address
    if target_port:
        args["targetPort"] = target_port

    args['label'] = as3types.f5label(health_monitor.name or health_monitor.id)

    return Monitor(**args)
def get_monitor(health_monitor, target_address=None, target_port=None):
    args = dict()

    # Standard Octavia monitor types
    if health_monitor.type == 'HTTP':
        args['monitorType'] = 'http'
    elif health_monitor.type == 'HTTPS':
        args['monitorType'] = 'https'
    elif health_monitor.type == 'PING':
        args['monitorType'] = 'icmp'
    elif health_monitor.type == 'TCP':
        args['monitorType'] = 'tcp'
        args['send'] = ''
        args['receive'] = ''
    elif health_monitor.type == 'TLS-HELLO':
        args['monitorType'] = 'external'
        args['script'] = TLS_HELLO_CHECK
        args['receive'] = 'UP'
    elif health_monitor.type == 'UDP-CONNECT':
        args['monitorType'] = 'udp'
        args['receive'] = ''
        args['send'] = ''

    # F5 specific monitory types
    elif health_monitor.type == 'SIP':
        args['monitorType'] = 'sip'
    elif health_monitor.type == 'SMTP':
        args['monitorType'] = 'smtp'
    elif health_monitor.type == 'TCP-HALF_OPEN':
        args['monitorType'] = 'tcp-half-open'
    elif health_monitor.type == 'LDAP':
        args['monitorType'] = 'ldap'
    elif health_monitor.type == 'DNS':
        args['monitorType'] = 'dns'
        args['queryName'] = health_monitor.domain_name
    # No Health monitor type available
    else:
        return {}

    if health_monitor.type == 'HTTP' or health_monitor.type == 'HTTPS':
        http_version = '1.0'
        if health_monitor.http_version:
            http_version = health_monitor.http_version
        send = "{} {} HTTP/{}\\r\\n".format(
            health_monitor.http_method,
            health_monitor.url_path,
            http_version
            )
        if health_monitor.domain_name:
            send += "Host: {}\\r\\n\\r\\n".format(
                health_monitor.domain_name)
        else:
            send += "\\r\\n"

        args['send'] = send
        args['receive'] = _get_recv_text(health_monitor)

    args["interval"] = health_monitor.delay
    timeout = int(health_monitor.fall_threshold) * int(health_monitor.delay) + 1
    # respect BigIP LTM maximum health monitor timeout of 900 seconds
    args["timeout"] = min(timeout, 900)
    if target_address:
        args["targetAddress"] = target_address
    if target_port:
        args["targetPort"] = target_port

    if CONF.f5_agent.profile_healthmonitor_tls and health_monitor.type == 'HTTPS':
        args["clientTLS"] = as3.BigIP(CONF.f5_agent.profile_healthmonitor_tls)

    args['label'] = as3types.f5label(health_monitor.name or health_monitor.id)

    return as3.Monitor(**args)
Example #10
0
def get_service(listener, cert_manager, esd_repository):
    """ Map Octavia listener -> AS3 Service

    :param listener: Octavia listener
    :param cert_manager: cert_manager wrapper instance
    :return: AS3 Service + additional AS3 application objects
    """

    # Entities is a list of tuples, which each describe AS3 objects
    # which may reference each other but do not form a hierarchy.
    entities = []
    vip = listener.load_balancer.vip
    project_id = listener.load_balancer.project_id
    label = as3types.f5label(listener.description or listener.id)
    service_args = {
        'virtualPort': listener.protocol_port,
        'virtualAddresses': [vip.ip_address],
        'persistenceMethods': [],
        'iRules': [],
        'policyEndpoint': [],
        'label': label
    }

    # Determine service type
    if listener.protocol == const.PROTOCOL_TCP:
        service_args['_servicetype'] = CONF.f5_agent.tcp_service_type
    # UDP
    elif listener.protocol == const.PROTOCOL_UDP:
        service_args['_servicetype'] = const.SERVICE_UDP
    # HTTP
    elif listener.protocol == const.PROTOCOL_HTTP:
        service_args['_servicetype'] = const.SERVICE_HTTP
    # HTTPS (non-terminated)
    elif listener.protocol == const.PROTOCOL_HTTPS:
        service_args['_servicetype'] = const.SERVICE_GENERIC
    # Proxy
    elif listener.protocol == const.PROTOCOL_PROXY:
        service_args['_servicetype'] = const.SERVICE_HTTP
        name, irule = m_irule.get_proxy_irule()
        service_args['iRules'].append(name)
        entities.append((name, irule))
    # Terminated HTTPS
    elif listener.protocol == const.PROTOCOL_TERMINATED_HTTPS:
        service_args['_servicetype'] = const.SERVICE_HTTPS
        service_args['serverTLS'] = m_tls.get_listener_name(listener.id)
        service_args['redirect80'] = False

        # Certificate Handling
        auth_name = None
        certificates = cert_manager.get_certificates(listener)
        if listener.client_ca_tls_certificate_id and listener.client_authentication != 'NONE':
            # Client Side Certificates
            try:
                auth_name, secret = cert_manager.load_secret(
                    project_id, listener.client_ca_tls_certificate_id)
                entities.append((auth_name,
                                 m_cert.get_ca_bundle(secret, auth_name,
                                                      auth_name)))
            except exceptions.CertificateRetrievalException as e:
                LOG.error("Error fetching certificate: %s", e)

        entities.append(
            (m_tls.get_listener_name(listener.id),
             m_tls.get_tls_server([cert['id'] for cert in certificates],
                                  auth_name, listener.client_authentication)))
        entities.extend([(cert['id'], cert['as3']) for cert in certificates])

    # add profile
    if CONF.f5_agent.profile_l4:
        service_args['profileL4'] = as3.BigIP(CONF.f5_agent.profile_l4)
    if CONF.f5_agent.profile_multiplex:
        service_args['profileMultiplex'] = as3.BigIP(
            CONF.f5_agent.profile_multiplex)

    if listener.connection_limit > 0:
        service_args['maxConnections'] = listener.connection_limit

    # Add default pool
    if listener.default_pool_id:
        pool = listener.default_pool
        if pool.provisioning_status != lib_consts.PENDING_DELETE:
            default_pool = m_pool.get_name(listener.default_pool_id)
            service_args['pool'] = default_pool

            # only consider Proxy pool, everything else is determined by listener type
            if pool.protocol == const.PROTOCOL_PROXY:
                name, irule = m_irule.get_proxy_irule()
                service_args['iRules'].append(name)
                entities.append((name, irule))

            # Support of backup members (realized as fallback host via http_profile), pick the first one
            backup_members = [
                member for member in pool.members if member.backup
            ]
            if backup_members:
                http_profile_name = m_member.get_name(backup_members[0].id)
                http_profile = as3.HTTP_Profile(
                    fallbackRedirect=backup_members[0].ip_address)
                service_args['profileHTTP'] = as3.Pointer(http_profile_name)
                entities.append((http_profile_name, http_profile))

        # Pool member certificate handling (TLS backends)
        if pool.tls_enabled:
            client_cert = None
            trust_ca = None
            crl_file = None

            service_args['clientTLS'] = m_tls.get_pool_name(pool.id)
            certificates = cert_manager.get_certificates(pool)
            if len(certificates) == 1:
                cert = certificates.pop()
                entities.append((cert['id'], cert['as3']))
                client_cert = cert['id']

            if pool.ca_tls_certificate_id:
                trust_ca, secret = cert_manager.load_secret(
                    project_id, pool.ca_tls_certificate_id)
                entities.append(
                    (trust_ca, m_cert.get_ca_bundle(secret, trust_ca,
                                                    trust_ca)))

            if pool.crl_container_id:
                # TODO: CRL currently not supported
                pass

            entities.append((m_tls.get_pool_name(pool.id),
                             m_tls.get_tls_client(trust_ca=trust_ca,
                                                  client_cert=client_cert,
                                                  crl_file=crl_file)))

    # Insert header irules
    for name, irule in m_irule.get_header_irules(listener.insert_headers):
        service_args['iRules'].append(name)
        entities.append((name, irule))

    # session persistence
    if listener.default_pool_id and listener.default_pool.session_persistence:
        persistence = listener.default_pool.session_persistence
        lb_algorithm = listener.default_pool.lb_algorithm

        if persistence.type == 'APP_COOKIE':
            name, obj_persist = m_persist.get_app_cookie(
                persistence.cookie_name)
            service_args['persistenceMethods'] = [as3.Pointer(name)]
            entities.append((name, obj_persist))
            if lb_algorithm == 'SOURCE_IP':
                service_args['fallbackPersistenceMethod'] = 'source-address'

        elif persistence.type == 'SOURCE_IP':
            if not persistence.persistence_timeout and not persistence.persistence_granularity:
                service_args['persistenceMethods'] = ['source-address']
            else:
                name, obj_persist = m_persist.get_source_ip(
                    persistence.persistence_timeout,
                    persistence.persistence_granularity)
                service_args['persistenceMethods'] = [as3.Pointer(name)]
                entities.append((name, obj_persist))

        elif persistence.type == 'HTTP_COOKIE':
            service_args['persistenceMethods'] = ['cookie']
            if lb_algorithm == 'SOURCE_IP':
                service_args['fallbackPersistenceMethod'] = 'source-address'

    # Map listener tags to ESDs
    for tag in listener.tags:

        # get ESD of same name
        esd = esd_repository.get_esd(tag)
        if esd is None:
            continue

        # enrich service with iRules and other things defined in ESD
        esd_entities = get_esd_entities(service_args['_servicetype'], esd)
        for entity_name in esd_entities:
            if entity_name == 'iRules':
                service_args['iRules'].extend(esd_entities['iRules'])
            else:
                service_args[entity_name] = esd_entities[entity_name]

    # Map special L7policies to ESDs
    # TODO: Remove this as soon as all customers have migrated their scripts.
    #  Triggering ESDs via L7policies is considered deprecated. Tags should be used instead. See the code above.
    for policy in listener.l7policies:

        # get ESD of same name
        esd = esd_repository.get_esd(policy.name)

        # Add ESD or regular endpoint policy
        if esd:

            # enrich service with iRules and other things defined in ESD
            esd_entities = get_esd_entities(service_args['_servicetype'], esd)
            for entity_name in esd_entities:
                if entity_name == 'iRules':
                    service_args['iRules'].extend(esd_entities['iRules'])
                else:
                    service_args[entity_name] = esd_entities[entity_name]

        elif policy.provisioning_status != lib_consts.PENDING_DELETE:
            # add a regular endpoint policy
            policy_name = m_policy.get_name(policy.id)

            # make endpoint policy object
            endpoint_policy = (policy_name,
                               m_policy.get_endpoint_policy(policy))
            entities.append(endpoint_policy)

            # reference endpoint policy object in service
            service_args['policyEndpoint'].append(policy_name)

    # Ensure no duplicate iRules
    service_args['iRules'] = list(set(service_args['iRules']))

    # create service object and fill in additional fields
    service = as3.Service(**service_args)

    # add service to entities and return
    entities.append((get_name(listener.id), service))
    return entities