# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance withv
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from libcloud.utils.py3 import ET
from libcloud.common.nttcis import NttCisConnection
from libcloud.common.nttcis import NttCisPool
from libcloud.common.nttcis import NttCisPoolMember
from libcloud.common.nttcis import NttCisVirtualListener
from libcloud.common.nttcis import NttCisVIPNode
from libcloud.common.nttcis import NttCisDefaultHealthMonitor
from libcloud.common.nttcis import NttCisPersistenceProfile
from libcloud.common.nttcis import \
    NttCisVirtualListenerCompatibility
from libcloud.common.nttcis import NttCisDefaultiRule
from libcloud.common.nttcis import API_ENDPOINTS
from libcloud.common.nttcis import DEFAULT_REGION
from libcloud.common.nttcis import TYPES_URN
from libcloud.common.nttcis import process_xml, get_params
from libcloud.utils.misc import reverse_dict
from libcloud.utils.xml import fixxpath, findtext, findall
from libcloud.loadbalancer.types import State
from libcloud.loadbalancer.base import Algorithm, Driver, \
    LoadBalancer, DEFAULT_ALGORITHM
from libcloud.loadbalancer.base import Member
from libcloud.loadbalancer.types import Provider


class NttCisLBDriver(Driver):
    """
    NttCis LB driver.
    """

    selected_region = None
    connectionCls = NttCisConnection
    name = 'NTTC-CIS Load Balancer'
    website = 'https://cloud.nttcis.com/'
    type = Provider.NTTCIS
    api_version = 1.0

    _VALUE_TO_ALGORITHM_MAP = {
        'ROUND_ROBIN': Algorithm.ROUND_ROBIN,
        'LEAST_CONNECTIONS_MEMBER': Algorithm.LEAST_CONNECTIONS_MEMBER,
        'LEAST_CONNECTIONS_NODE': Algorithm.LEAST_CONNECTIONS_NODE,
        'OBSERVED_MEMBER': Algorithm.OBSERVED_MEMBER,
        'OBSERVED_NODE': Algorithm.OBSERVED_NODE,
        'PREDICTIVE_MEMBER': Algorithm.PREDICTIVE_MEMBER,
        'PREDICTIVE_NODE': Algorithm.PREDICTIVE_NODE
    }
    _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)

    _VALUE_TO_STATE_MAP = {
        'NORMAL': State.RUNNING,
        'PENDING_ADD': State.PENDING,
        'PENDING_CHANGE': State.PENDING,
        'PENDING_DELETE': State.PENDING,
        'FAILED_ADD': State.ERROR,
        'FAILED_CHANGE': State.ERROR,
        'FAILED_DELETE': State.ERROR,
        'REQUIRES_SUPPORT': State.ERROR
    }

    def __init__(self, key, network_domain_id, secret=None, secure=True,
                 host=None, port=None, api_version=None,
                 region=DEFAULT_REGION, **kwargs):

        self.network_domain_id = network_domain_id

        if region not in API_ENDPOINTS and host is None:
            raise ValueError(
                'Invalid region: %s, no host specified' % (region))
        if region is not None:
            self.selected_region = API_ENDPOINTS[region]

        super(NttCisLBDriver, self).__init__(key=key, secret=secret,
                                             secure=secure, host=host,
                                             port=port,
                                             api_version=api_version,
                                             region=region,
                                             **kwargs)

    def _ex_connection_class_kwargs(self):
        """
            Add the region to the kwargs before the connection is instantiated
        """

        kwargs = super(NttCisLBDriver,
                       self)._ex_connection_class_kwargs()
        kwargs['region'] = self.selected_region
        return kwargs

    def create_balancer(self, name, listener_port=None, port=None,
                        protocol=None, algorithm=None, members=None,
                        optimization_profile="TCP",
                        ex_listener_ip_address=None):
        """

        Create a new load balancer instance

        :param name: Name of the new load balancer (required)
        :type  name: ``str``

        :param listener_port: An integer in the range of 1-65535.
                              If not supplied, it will be taken to
                              mean 'Any Port'
        :type  port: ``int

        :param port: An integer in the range of 1-65535. If not supplied,
                     it will be taken to mean 'Any Port'  Assumed that
                     node ports will different from listener port.
        :type  port: ``int``

        :param protocol: Loadbalancer protocol, defaults to http.
        :type  protocol: ``str``

        :param members: list of Members to attach to balancer (optional)
        :type  members: ``list`` of :class:`Member`

        :param algorithm: Load balancing algorithm, defaults to ROUND_ROBIN.
        :type algorithm: :class:`.Algorithm`

        :param optimization_profile: For STANDARD type and protocol TCP
                                     an optimization type of TCP, LAN_OPT,
                                     WAN_OPT, MOBILE_OPT, or TCP_LEGACY is
                                     required. Default is TCP
        :type  protcol: ``str``

        :param ex_listener_ip_address: Must be a valid IPv4 in dot-decimal
                                       notation (x.x.x.x).
        :type ex_listener_ip_address: ``str``

        :rtype: :class:`LoadBalancer`
        """

        network_domain_id = self.network_domain_id
        if protocol is None:
            protocol = 'http'
        if algorithm is None:
            algorithm = DEFAULT_ALGORITHM

        # Create a pool first

        pool = self.ex_create_pool(
            network_domain_id=network_domain_id,
            name=name,
            ex_description=None,
            balancer_method=self._ALGORITHM_TO_VALUE_MAP[algorithm])

        # Attach the members to the pool as nodes
        if members is not None:
            for member in members:
                if not isinstance(member, Member):
                    member = self.ex_create_node(
                        network_domain_id=network_domain_id,
                        name=member.name,
                        ip=member.private_ips[0],
                        ex_description=None)
                self.ex_create_pool_member(
                    pool=pool,
                    node=member,
                    port=port)

        # Create the virtual listener (balancer)
        listener = self.ex_create_virtual_listener(
            network_domain_id=network_domain_id,
            name=name,
            ex_description=name,
            port=listener_port,
            pool=pool,
            protocol=protocol,
            optimization_profile=optimization_profile,
            listener_ip_address=ex_listener_ip_address)

        return LoadBalancer(
            id=listener.id,
            name=listener.name,
            state=State.RUNNING,
            ip=listener.ip,
            port=port,
            driver=self,
            extra={'pool_id': pool.id,
                   'network_domain_id': network_domain_id,
                   'listener_ip_address': ex_listener_ip_address}
        )

    def ex_update_listener(self, virtual_listener, **kwargs):
        """
        Update a current virtual listener.
        :param virtual_listener: The listener to be updated
        :return: The edited version of the listener
        """
        edit_listener_elm = ET.Element('editVirtualListener',
                                       {'xmlns': TYPES_URN,
                                        'id': virtual_listener.id,
                                        'xmlns:xsi':
                                            "http://www.w3.org/2001/"
                                            "XMLSchema-instance"})
        for k, v in kwargs.items():
            if v is None:
                ET.SubElement(edit_listener_elm, k, {'xsi:nil': 'true'})
            else:
                ET.SubElement(edit_listener_elm, k).text = v

        result = self.connection.request_with_orgId_api_2(
            'networkDomainVip/editVirtualListener',
            method='POST',
            data=ET.tostring(edit_listener_elm)).object
        response_code = findtext(result, 'responseCode', TYPES_URN)
        return response_code in ['IN_PROGRESS', 'OK']

    def list_balancers(self, ex_network_domain_id=None):
        """
        List all loadbalancers inside a geography or in given network.

        In NTTC-CIS terminology these are known as virtual listeners

        :param ex_network_domain_id: UUID of Network Domain
               if not None returns only balancers in the given network
               if None then returns all pools for the organization
        :type  ex_network_domain_id: ``str``

        :rtype: ``list`` of :class:`LoadBalancer`
        """
        params = None
        if ex_network_domain_id is not None:
            params = {"networkDomainId": ex_network_domain_id}

        return self._to_balancers(
            self.connection
                .request_with_orgId_api_2('networkDomainVip/virtualListener',
                                          params=params).object)

    def get_balancer(self, balancer_id):
        """
        Return a :class:`LoadBalancer` object.

        :param balancer_id: id of a load balancer you want to fetch
        :type  balancer_id: ``str``

        :rtype: :class:`LoadBalancer`
        """

        bal = self.connection \
            .request_with_orgId_api_2('networkDomainVip/virtualListener/%s'
                                      % balancer_id).object
        return self._to_balancer(bal)

    def list_protocols(self):
        """
        Return a list of supported protocols.

        Since all protocols are support by NTTC-CIS, this is a list
        of common protocols.

        :rtype: ``list`` of ``str``
        """
        return ['http', 'https', 'tcp', 'udp', 'ftp', 'smtp']

    def balancer_list_members(self, balancer):
        """
        Return list of members attached to balancer.

        In NTTC-CIS terminology these are the members of the pools
        within a virtual listener.

        :param balancer: LoadBalancer which should be used
        :type  balancer: :class:`LoadBalancer`

        :rtype: ``list`` of :class:`Member`
        """
        pool_members = self.ex_get_pool_members(balancer.extra['pool_id'])
        members = []
        for pool_member in pool_members:
            members.append(Member(
                id=pool_member.id,
                ip=pool_member.ip,
                port=pool_member.port,
                balancer=balancer,
                extra=None
            ))
        return members

    def balancer_attach_member(self, balancer, member):
        """
        Attach a member to balancer

        :param balancer: LoadBalancer which should be used
        :type  balancer: :class:`LoadBalancer`

        :param member: Member to join to the balancer
        :type member: :class:`Member`

        :return: Member after joining the balancer.
        :rtype: :class:`Member`
        """
        node = self.ex_create_node(
            network_domain_id=balancer.extra['network_domain_id'],
            name='Member.' + member.ip,
            ip=member.ip,
            ex_description=''
        )
        if node is False:
            return False
        pool = self.ex_get_pool(balancer.extra['pool_id'])
        pool_member = self.ex_create_pool_member(
            pool=pool,
            node=node,
            port=member.port)
        member.id = pool_member.id
        return member

    def balancer_detach_member(self, balancer, member):
        """
        Detach member from balancer

        :param balancer: LoadBalancer which should be used
        :type  balancer: :class:`LoadBalancer`

        :param member: Member which should be used
        :type member: :class:`Member`

        :return: ``True`` if member detach was successful, otherwise ``False``.
        :rtype: ``bool``
        """
        create_pool_m = ET.Element('removePoolMember', {'xmlns': TYPES_URN,
                                                        'id': member.id})

        result = self.connection.request_with_orgId_api_2(
            'networkDomainVip/removePoolMember',
            method='POST',
            data=ET.tostring(create_pool_m)).object
        response_code = findtext(result, 'responseCode', TYPES_URN)
        return response_code in ['IN_PROGRESS', 'OK']

    def destroy_balancer(self, balancer):
        """
        Destroy a load balancer (virtual listener)

        :param balancer: LoadBalancer which should be used
        :type  balancer: :class:`LoadBalancer`

        :return: ``True`` if the destroy was successful, otherwise ``False``.
        :rtype: ``bool``
        """
        delete_listener = ET.Element('deleteVirtualListener',
                                     {'xmlns': TYPES_URN,
                                      'id': balancer.id})

        result = self.connection.request_with_orgId_api_2(
            'networkDomainVip/deleteVirtualListener',
            method='POST',
            data=ET.tostring(delete_listener)).object
        response_code = findtext(result, 'responseCode', TYPES_URN)
        return response_code in ['IN_PROGRESS', 'OK']

    def ex_set_current_network_domain(self, network_domain_id):
        """
        Set the network domain (part of the network) of the driver

        :param network_domain_id: ID of the pool (required)
        :type  network_domain_id: ``str``
        """
        self.network_domain_id = network_domain_id

    def ex_get_current_network_domain(self):
        """
        Get the current network domain ID of the driver.

        :return: ID of the network domain
        :rtype: ``str``
        """
        return self.network_domain_id

    def ex_create_pool_member(self, pool, node, port=None):
        """
        Create a new member in an existing pool from an existing node

        :param pool: Instance of ``NttCisPool`` (required)
        :type  pool: ``NttCisPool``

        :param node: Instance of ``NttCisVIPNode`` (required)
        :type  node: ``NttCisVIPNode``

        :param port: Port the the service will listen on
        :type  port: ``str``

        :return: The node member, instance of ``NttCisPoolMember``
        :rtype: ``NttCisPoolMember``
        """
        create_pool_m = ET.Element('addPoolMember', {'xmlns': TYPES_URN})
        ET.SubElement(create_pool_m, "poolId").text = pool.id
        ET.SubElement(create_pool_m, "nodeId").text = node.id
        if port is not None:
            ET.SubElement(create_pool_m, "port").text = str(port)
        ET.SubElement(create_pool_m, "status").text = 'ENABLED'
        response = self.connection.request_with_orgId_api_2(
            'networkDomainVip/addPoolMember',
            method='POST',
            data=ET.tostring(create_pool_m)).object

        member_id = None
        node_name = None
        for info in findall(response, 'info', TYPES_URN):
            if info.get('name') == 'poolMemberId':
                member_id = info.get('value')
            if info.get('name') == 'nodeName':
                node_name = info.get('value')

        return NttCisPoolMember(
            id=member_id,
            name=node_name,
            status=State.RUNNING,
            ip=node.ip,
            port=port,
            node_id=node.id
        )

    def ex_create_node(self,
                       network_domain_id,
                       name,
                       ip,
                       ex_description=None,
                       connection_limit=25000,
                       connection_rate_limit=2000):
        """
        Create a new node

        :param network_domain_id: Network Domain ID (required)
        :type  name: ``str``

        :param name: name of the node (required)
        :type  name: ``str``

        :param ip: IPv4 address of the node (required)
        :type  ip: ``str``

        :param ex_description: Description of the node (required)
        :type  ex_description: ``str``

        :param connection_limit: Maximum number
                of concurrent connections per sec
        :type  connection_limit: ``int``

        :param connection_rate_limit: Maximum number of concurrent sessions
        :type  connection_rate_limit: ``int``

        :return: Instance of ``NttCisVIPNode``
        :rtype: ``NttCisVIPNode``
        """
        create_node_elm = ET.Element('createNode', {'xmlns': TYPES_URN})
        ET.SubElement(create_node_elm, "networkDomainId") \
            .text = network_domain_id
        ET.SubElement(create_node_elm, "name").text = name
        if ex_description is not None:
            ET.SubElement(create_node_elm, "description").text \
                = str(ex_description)
        ET.SubElement(create_node_elm, "ipv4Address").text = ip
        ET.SubElement(create_node_elm, "status").text = 'ENABLED'
        ET.SubElement(create_node_elm, "connectionLimit") \
            .text = str(connection_limit)
        ET.SubElement(create_node_elm, "connectionRateLimit") \
            .text = str(connection_rate_limit)

        response = self.connection.request_with_orgId_api_2(
            action='networkDomainVip/createNode',
            method='POST',
            data=ET.tostring(create_node_elm)).object

        node_id = None
        node_name = None
        for info in findall(response, 'info', TYPES_URN):
            if info.get('name') == 'nodeId':
                node_id = info.get('value')
            if info.get('name') == 'name':
                node_name = info.get('value')
        return NttCisVIPNode(
            id=node_id,
            name=node_name,
            status=State.RUNNING,
            ip=ip
        )

    def ex_update_node(self, node):
        """
        Update the properties of a node

        :param pool: The instance of ``NttCisNode`` to update
        :type  pool: ``NttCisNode``

        :return: The instance of ``NttCisNode``
        :rtype: ``NttCisNode``
        """
        create_node_elm = ET.Element('editNode', {'xmlns': TYPES_URN})
        create_node_elm.set('id', node.id)
        ET.SubElement(create_node_elm, 'healthMonitorId') \
            .text = node.health_monitor_id
        ET.SubElement(create_node_elm, "connectionLimit") \
            .text = str(node.connection_limit)
        ET.SubElement(create_node_elm, "connectionRateLimit") \
            .text = str(node.connection_rate_limit)

        self.connection.request_with_orgId_api_2(
            action='networkDomainVip/editNode',
            method='POST',
            data=ET.tostring(create_node_elm)).object
        return node

    def ex_set_node_state(self, node, enabled):
        """
        Change the state of a node (enable/disable)

        :param pool: The instance of ``NttCisNode`` to update
        :type  pool: ``NttCisNode``

        :param enabled: The target state of the node
        :type  enabled: ``bool``

        :return: The instance of ``NttCisNode``
        :rtype: ``NttCisNode``
        """
        create_node_elm = ET.Element('editNode', {'xmlns': TYPES_URN})
        ET.SubElement(create_node_elm, "status") \
            .text = "ENABLED" if enabled is True else "DISABLED"

        self.connection.request_with_orgId_api_2(
            action='networkDomainVip/editNode',
            method='POST',
            data=ET.tostring(create_node_elm)).object
        return node

    def ex_create_pool(self,
                       network_domain_id,
                       name,
                       balancer_method,
                       ex_description,
                       health_monitors=None,
                       service_down_action='NONE',
                       slow_ramp_time=30):
        """
        Create a new pool

        :param network_domain_id: Network Domain ID (required)
        :type  name: ``str``

        :param name: name of the node (required)
        :type  name: ``str``

        :param balancer_method: The load balancer algorithm (required)
        :type  balancer_method: ``str``

        :param ex_description: Description of the node (required)
        :type  ex_description: ``str``

        :param health_monitors: A list of health monitors to use for the pool.
        :type  health_monitors: ``list`` of
            :class:`NttCisDefaultHealthMonitor`

        :param service_down_action: What to do when node
                                    is unavailable NONE, DROP or RESELECT
        :type  service_down_action: ``str``

        :param slow_ramp_time: Number of seconds to stagger ramp up of nodes
        :type  slow_ramp_time: ``int``

        :return: Instance of ``NttCisPool``
        :rtype: ``NttCisPool``
        """
        # Names cannot contain spaces.
        name.replace(' ', '_')
        create_node_elm = ET.Element('createPool', {'xmlns': TYPES_URN})
        ET.SubElement(create_node_elm, "networkDomainId") \
            .text = network_domain_id
        ET.SubElement(create_node_elm, "name").text = name
        ET.SubElement(create_node_elm, "description").text \
            = str(ex_description)
        ET.SubElement(create_node_elm, "loadBalanceMethod") \
            .text = str(balancer_method)

        if health_monitors is not None:
            for monitor in health_monitors:
                ET.SubElement(create_node_elm, "healthMonitorId") \
                    .text = str(monitor.id)

        ET.SubElement(create_node_elm, "serviceDownAction") \
            .text = service_down_action
        ET.SubElement(create_node_elm, "slowRampTime").text \
            = str(slow_ramp_time)

        response = self.connection.request_with_orgId_api_2(
            action='networkDomainVip/createPool',
            method='POST',
            data=ET.tostring(create_node_elm)).object

        pool_id = None
        for info in findall(response, 'info', TYPES_URN):
            if info.get('name') == 'poolId':
                pool_id = info.get('value')

        return NttCisPool(
            id=pool_id,
            name=name,
            description=ex_description,
            status=State.RUNNING,
            load_balance_method=str(balancer_method),
            health_monitor_id=None,
            service_down_action=service_down_action,
            slow_ramp_time=str(slow_ramp_time)
        )

    def ex_create_virtual_listener(self,
                                   network_domain_id,
                                   name,
                                   ex_description,
                                   port=None,
                                   pool=None,
                                   listener_ip_address=None,
                                   persistence_profile=None,
                                   fallback_persistence_profile=None,
                                   irule=None,
                                   protocol='TCP',
                                   optimization_profile="TCP",
                                   connection_limit=25000,
                                   connection_rate_limit=2000,
                                   source_port_preservation='PRESERVE'):
        """
        Create a new virtual listener (load balancer)

        :param network_domain_id: Network Domain ID (required)
        :type  name: ``str``

        :param name: name of the listener (required)
        :type  name: ``str``

        :param ex_description: Description of the node (required)
        :type  ex_description: ``str``

        :param port: An integer in the range of 1-65535. If not supplied,
                     it will be taken to mean 'Any Port'
        :type  port: ``int``

        :param pool: The pool to use for the listener
        :type  pool: :class:`NttCisPool`

        :param listener_ip_address: The IPv4 Address of the virtual listener
        :type  listener_ip_address: ``str``

        :param persistence_profile: Persistence profile
        :type  persistence_profile: :class:`NttCisPersistenceProfile`

        :param fallback_persistence_profile: Fallback persistence profile
        :type  fallback_persistence_profile:
            :class:`NttCisPersistenceProfile`

        :param irule: The iRule to apply
        :type  irule: :class:`NttCisDefaultiRule`

        :param protocol: For STANDARD type, ANY, TCP or UDP
                         for PERFORMANCE_LAYER_4 choice of ANY, TCP, UDP, HTTP
        :type  protcol: ``str``

        :param optimization_profile: For STANDARD type and protocol
                                     TCP an optimization type of TCP,
                                     LAN_OPT, WAN_OPT, MOBILE_OPT,
                                     or TCP_LEGACY is required.
                                     Default is 'TCP'.
        :type  protcol: ``str``

        :param connection_limit: Maximum number
                                of concurrent connections per sec
        :type  connection_limit: ``int``

        :param connection_rate_limit: Maximum number of concurrent sessions
        :type  connection_rate_limit: ``int``

        :param source_port_preservation: Choice of PRESERVE,
                                         PRESERVE_STRICT or CHANGE
        :type  source_port_preservation: ``str``

        :return: Instance of the listener
        :rtype: ``NttCisVirtualListener``
        """
        if (port == 80) or (port == 443):
            listener_type = 'PERFORMANCE_LAYER_4'
        else:
            listener_type = 'STANDARD'

        if listener_type == 'STANDARD' and optimization_profile is None:
            raise ValueError(
                " CONFIGURATION_NOT_SUPPORTED: optimizationProfile is"
                " required for type STANDARD and protocol TCP")

        create_node_elm = ET.Element('createVirtualListener',
                                     {'xmlns': TYPES_URN})
        ET.SubElement(create_node_elm, "networkDomainId") \
            .text = network_domain_id
        ET.SubElement(create_node_elm, "name").text = name
        ET.SubElement(create_node_elm, "description").text = \
            str(ex_description)
        ET.SubElement(create_node_elm, "type").text = listener_type
        ET.SubElement(create_node_elm, "protocol") \
            .text = protocol
        if listener_ip_address is not None:
            ET.SubElement(create_node_elm, "listenerIpAddress").text = \
                str(listener_ip_address)
        if port is not None:
            ET.SubElement(create_node_elm, "port").text = str(port)
        ET.SubElement(create_node_elm, "enabled").text = 'true'
        ET.SubElement(create_node_elm, "connectionLimit") \
            .text = str(connection_limit)
        ET.SubElement(create_node_elm, "connectionRateLimit") \
            .text = str(connection_rate_limit)
        ET.SubElement(create_node_elm, "sourcePortPreservation") \
            .text = source_port_preservation
        if pool is not None:
            ET.SubElement(create_node_elm, "poolId") \
                .text = pool.id
        if persistence_profile is not None:
            ET.SubElement(create_node_elm, "persistenceProfileId") \
                .text = persistence_profile.id
        if optimization_profile is not None:
            ET.SubElement(create_node_elm, 'optimizationProfile').text = \
                optimization_profile
        if fallback_persistence_profile is not None:
            ET.SubElement(create_node_elm, "fallbackPersistenceProfileId") \
                .text = fallback_persistence_profile.id
        if irule is not None:
            ET.SubElement(create_node_elm, "iruleId") \
                .text = irule.id

        response = self.connection.request_with_orgId_api_2(
            action='networkDomainVip/createVirtualListener',
            method='POST',
            data=ET.tostring(create_node_elm)).object

        virtual_listener_id = None
        virtual_listener_ip = None
        for info in findall(response, 'info', TYPES_URN):
            if info.get('name') == 'virtualListenerId':
                virtual_listener_id = info.get('value')
            if info.get('name') == 'listenerIpAddress':
                virtual_listener_ip = info.get('value')

        return NttCisVirtualListener(
            id=virtual_listener_id,
            name=name,
            ip=virtual_listener_ip,
            status=State.RUNNING
        )

    def ex_import_ssl_domain_certificate(self, network_domain_id,
                                         name, crt_file, key_file,
                                         description=None):
        """
        Import an ssl cert for ssl offloading onto the the load balancer

        :param network_domain_id:  The Network Domain's Id.
        :type network_domain_id: ``str``
        :param name: The name of the ssl certificate
        :type name: ``str``
        :param crt_file: The complete path to the certificate file
        :type crt_file: ``str``
        :param key_file: The complete pathy to the key file
        :type key_file: ``str``
        :param description: (Optional) A description of the certificate
        :type `description: `str``
        :return: ``bool``
        """
        try:
            import OpenSSL
        except ImportError:
            raise ImportError('Missing "OpenSSL" dependency. You can install '
                              'it using pip - pip install pyopenssl')

        with open(crt_file) as fp:
            c = OpenSSL.crypto.load_certificate(
                OpenSSL.crypto.FILETYPE_PEM, fp.read())

        cert = OpenSSL.crypto.dump_certificate(
            OpenSSL.crypto.FILETYPE_PEM, c).decode(encoding='utf-8')

        with open(key_file) as fp:
            k = OpenSSL.crypto.load_privatekey(
                OpenSSL.crypto.FILETYPE_PEM, fp.read())

        key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, k) \
            .decode(encoding='utf-8')
        cert_elem = ET.Element("importSslDomainCertificate",
                               {"xmlns": TYPES_URN})
        ET.SubElement(cert_elem, "networkDomainId").text = network_domain_id
        ET.SubElement(cert_elem, "name").text = name
        if description is not None:
            ET.SubElement(cert_elem, "description").text = description
        ET.SubElement(cert_elem, "key").text = key
        ET.SubElement(cert_elem, "certificate").text = cert
        result = self.connection.request_with_orgId_api_2(
            'networkDomainVip/importSslDomainCertificate',
            method='POST',
            data=ET.tostring(cert_elem)).object
        response_code = findtext(result, 'responseCode', TYPES_URN)
        return response_code in ['IN_PROGRESS', 'OK']

    def ex_delete_ssl_domain_certificate(self, dom_cert_id):
        """
        Deletes an SSL domain certificate

        :param dom_cert_id: Id of certificate to delete
        :type dom_cert_id: ``str``
        :return: ``bool``
        """
        del_dom_cert_elem = ET.Element("deleteSslDomainCertificate",
                                       {"id": dom_cert_id,
                                        "xmlns": TYPES_URN})
        result = self.connection.request_with_orgId_api_2(
            'networkDomainVip/deleteSslDomainCertificate',
            method='POST',
            data=ET.tostring(del_dom_cert_elem)).object
        response_code = findtext(result, 'responseCode', TYPES_URN)
        return response_code in ['IN_PROGRESS', 'OK']

    def ex_import_ssl_cert_chain(self, network_domain_id, name,
                                 chain_crt_file, description=None):
        """
        Import an ssl certificate chain for ssl offloading onto
        the the load balancer

        :param network_domain_id:  The Network Domain's Id.
        :type network_domain_id: ``str``
        :param name: The name of the ssl certificate chain
        :type name: ``str``
        :param chain_crt_file: The complete path to the certificate chain file
        :type chain_crt_file: ``str``
        :param description: (Optional) A description of the certificate chain
        :type description: ``str``
        :return: ``bool``
        """
        try:
            import OpenSSL
            from OpenSSL import crypto
        except ImportError:
            raise ImportError('Missing "OpenSSL" dependency. You can install '
                              'it using pip - pip install pyopenssl')

        c = crypto.load_certificate(
            crypto.FILETYPE_PEM, open(chain_crt_file).read())
        cert = OpenSSL.crypto.dump_certificate(
            crypto.FILETYPE_PEM, c).decode(encoding='utf-8')
        cert_chain_elem = ET.Element("importSslCertificateChain",
                                     {"xmlns": TYPES_URN})
        ET.SubElement(cert_chain_elem, "networkDomainId") \
            .text = network_domain_id
        ET.SubElement(cert_chain_elem, "name").text = name
        if description is not None:
            ET.SubElement(cert_chain_elem, "description").text = description
        ET.SubElement(cert_chain_elem, "certificateChain").text = cert
        result = self.connection.request_with_orgId_api_2(
            "networkDomainVip/importSslCertificateChain",
            method="POST",
            data=ET.tostring(cert_chain_elem)).object
        response_code = findtext(result, 'responseCode', TYPES_URN)
        return response_code in ['IN_PROGRESS', 'OK']

    def ex_delete_ssl_certificate_chain(self, cert_chain_id):
        """
        Deletes a certificate chain

        :param cert_chain_id: Id of certificate chain to delete
        :type cert_chain_id: ``str``
        :return ``bool``
        """
        del_cert_chain_elem = ET.Element("deleteSslCertificateChain",
                                         {"id": cert_chain_id,
                                          "xmlns": TYPES_URN})
        result = self.connection.request_with_orgId_api_2(
            "networkDomainVip/deleteSslCertificateChain",
            method="POST",
            data=ET.tostring(del_cert_chain_elem)).object
        response_code = findtext(result, 'responseCode', TYPES_URN)
        return response_code in ['IN_PROGRESS', 'OK']

    def ex_create_ssl_offload_profile(self, netowrk_domain_id,
                                      name, ssl_domain_cert_id,
                                      description=None,
                                      ciphers=None,
                                      ssl_cert_chain_id=None):
        """
        Creates an SSL Offload profile

        :param network_domain_id: The network domain's Id
        :type netowrk_domain_id: ``str``
        :param name: Offload profile's name
        :type name: ``str``
        :param ssl_domain_cert_id: Certificate's Id
        :type ssl_domain_cert_id: ``str``
        :param description: (Optional) Profile's description
        :type description: ``str``
        :param ciphers: (Optional) The default cipher string is:
        "MEDIUM:HIGH:!EXPORT:!ADH:!MD5:!RC4:!SSLv2:!SSLv3:
        !ECDHE+AES-GCM:!ECDHE+AES:!ECDHE+3DES:!ECDHE_ECDSA:
        !ECDH_RSA:!ECDH_ECDSA:@SPEED" It is possible to choose just a subset
        of this string
        :type ciphers: ``str``
        :param ssl_cert_chain_id: (Optional) Bind the certificate
        chain to the profile.
        :type ssl_cert_chain_id: `str``
        :returns: ``bool``
        """
        ssl_offload_elem = ET.Element("createSslOffloadProfile",
                                      {"xmlns": TYPES_URN})
        ET.SubElement(ssl_offload_elem, "networkDomainId")\
            .text = netowrk_domain_id
        ET.SubElement(ssl_offload_elem, "name").text = name
        if description is not None:
            ET.SubElement(ssl_offload_elem, "description")\
                .text = description
        if ciphers is not None:
            ET.SubElement(ssl_offload_elem, "ciphers").text = ciphers
        ET.SubElement(ssl_offload_elem, "sslDomainCertificateId") \
            .text = ssl_domain_cert_id
        if ssl_cert_chain_id is not None:
            ET.SubElement(ssl_offload_elem, "sslCertificateChainId") \
                .text = ssl_cert_chain_id
        result = self.connection.request_with_orgId_api_2(
            "networkDomainVip/createSslOffloadProfile",
            method="POST",
            data=ET.tostring(ssl_offload_elem)).object
        response_code = findtext(result, 'responseCode', TYPES_URN)
        return response_code in ['IN_PROGRESS', 'OK']

    def ex_edit_ssl_offload_profile(self, profile_id,
                                    name, ssl_domain_cert_id,
                                    description=None,
                                    ciphers=None,
                                    ssl_cert_chain_id=None):
        """
        The function edits the ssl offload profile

        :param profil_id: The id of the profile to be edited
        :type profile_id: ``str``
        :param name: The name of the profile, new name or previous name
        :type name: ``str``
        :param ssl_domain_cert_id: The certificate id to use, new or current
        :type ssl_domain_cert_id: ``str``
        :param description: (Optional) Profile's description
        :type description: ``str``
        :param ciphers: (Optional) String of acceptable ciphers to use
        :type ciphers: ``str``
        :param ssl_cert_chain_id: If using a certificate chain
        or changing to a new one
        :type: ssl_cert_chain_id: ``str``
        :returns: ``bool``
        """
        ssl_offload_elem = ET.Element("editSslOffloadProfile",
                                      {"xmlns": TYPES_URN, "id": profile_id})
        ET.SubElement(ssl_offload_elem, "name").text = name
        if description is not None:
            ET.SubElement(ssl_offload_elem, "description").text = description
        if ciphers is not None:
            ET.SubElement(ssl_offload_elem, "ciphers").text = ciphers
        ET.SubElement(ssl_offload_elem, "sslDomainCertificateId") \
            .text = ssl_domain_cert_id
        if ssl_cert_chain_id is not None:
            ET.SubElement(ssl_offload_elem, "sslCertificateChainId") \
                .text = ssl_cert_chain_id
        result = self.connection.request_with_orgId_api_2(
            "networkDomainVip/editSslOffloadProfile",
            method="POST",
            data=ET.tostring(ssl_offload_elem)).object
        response_code = findtext(result, 'responseCode', TYPES_URN)
        return response_code in ['IN_PROGRESS', 'OK']

    def ex_delete_ssl_offload_profile(self, profile_id):
        """
        Delete an offload profile

        :param profile_id: Id of profile to be deleted
        :type profile_id: ``str``
        :returns: ``bool``
        """
        del_profile_elem = ET.Element("deleteSslOffloadProfile",
                                      {"id": profile_id, "xmlns": TYPES_URN})
        result = self.connection.request_with_orgId_api_2(
            "networkDomainVip/deleteSslOffloadProfile",
            method="POST",
            data=ET.tostring(del_profile_elem)).object
        response_code = findtext(result, "responseCode", TYPES_URN)
        return response_code in ["IN_PROGRESS", "OK"]

    def ex_get_pools(self, ex_network_domain_id=None):
        """
        Get all of the pools inside the current geography or
        in given network.

        :param ex_network_domain_id: UUID of Network Domain
               if not None returns only balancers in the given network
               if None then returns all pools for the organization
        :type  ex_network_domain_id: ``str``

        :return: Returns a ``list`` of type ``NttCisPool``
        :rtype: ``list`` of ``NttCisPool``
        """
        params = None

        if ex_network_domain_id is not None:
            params = {"networkDomainId": ex_network_domain_id}

        pools = self.connection \
            .request_with_orgId_api_2('networkDomainVip/pool',
                                      params=params).object

        return self._to_pools(pools)

    def ex_get_pool(self, pool_id):
        """
        Get a specific pool inside the current geography

        :param pool_id: The identifier of the pool
        :type  pool_id: ``str``

        :return: Returns an instance of ``NttCisPool``
        :rtype: ``NttCisPool``
        """
        pool = self.connection \
            .request_with_orgId_api_2('networkDomainVip/pool/%s'
                                      % pool_id).object
        return self._to_pool(pool)

    def ex_update_pool(self, pool):
        """
        Update the properties of an existing pool
        only method, serviceDownAction and slowRampTime are updated

        :param pool: The instance of ``NttCisPool`` to update
        :type  pool: ``NttCisPool``

        :return: ``True`` for success, ``False`` for failure
        :rtype: ``bool``
        """
        create_node_elm = ET.Element('editPool', {'xmlns': TYPES_URN})
        create_node_elm.set('id', pool.id)
        ET.SubElement(create_node_elm, "loadBalanceMethod") \
            .text = str(pool.load_balance_method)
        ET.SubElement(create_node_elm, 'healthMonitorId').text \
            = pool.health_monitor_id
        ET.SubElement(create_node_elm, "serviceDownAction") \
            .text = pool.service_down_action
        ET.SubElement(create_node_elm, "slowRampTime").text \
            = str(pool.slow_ramp_time)

        response = self.connection.request_with_orgId_api_2(
            action='networkDomainVip/editPool',
            method='POST',
            data=ET.tostring(create_node_elm)).object
        response_code = findtext(response, 'responseCode', TYPES_URN)
        return response_code in ['IN_PROGRESS', 'OK']

    def ex_destroy_pool(self, pool):
        """
        Destroy an existing pool

        :param pool: The instance of ``NttCisPool`` to destroy
        :type  pool: ``NttCisPool``

        :return: ``True`` for success, ``False`` for failure
        :rtype: ``bool``
        """
        destroy_request = ET.Element('deletePool',
                                     {'xmlns': TYPES_URN,
                                      'id': pool.id})

        result = self.connection.request_with_orgId_api_2(
            action='networkDomainVip/deletePool',
            method='POST',
            data=ET.tostring(destroy_request)).object
        response_code = findtext(result, 'responseCode', TYPES_URN)
        return response_code in ['IN_PROGRESS', 'OK']

    def ex_get_pool_members(self, pool_id):
        """
        Get the members of a pool

        :param pool: The instance of a pool
        :type  pool: ``NttCisPool``

        :returns: Returns an ``list`` of ``NttCisPoolMember``
        :rtype: ``list`` of ``NttCisPoolMember``
        """
        members = self.connection \
            .request_with_orgId_api_2('networkDomainVip/poolMember?poolId=%s'
                                      % pool_id).object
        return self._to_members(members)

    def ex_get_pool_member(self, pool_member_id):
        """
        Get a specific member of a pool

        :param pool: The id of a pool member
        :type  pool: ``str``

        :return: Returns an instance of ``NttCisPoolMember``
        :rtype: ``NttCisPoolMember``
        """
        member = self.connection \
            .request_with_orgId_api_2('networkDomainVip/poolMember/%s'
                                      % pool_member_id).object
        return self._to_member(member)

    def ex_set_pool_member_state(self, member, enabled=True):
        request = ET.Element('editPoolMember',
                             {'xmlns': TYPES_URN,
                              'id': member.id})
        state = "ENABLED" if enabled is True else "DISABLED"
        ET.SubElement(request, 'status').text = state

        result = self.connection.request_with_orgId_api_2(
            action='networkDomainVip/editPoolMember',
            method='POST',
            data=ET.tostring(request)).object

        response_code = findtext(result, 'responseCode', TYPES_URN)
        return response_code in ['IN_PROGRESS', 'OK']

    def ex_destroy_pool_member(self, member, destroy_node=False):
        """
        Destroy a specific member of a pool

        :param pool: The instance of a pool member
        :type  pool: ``NttCisPoolMember``

        :param destroy_node: Also destroy the associated node
        :type  destroy_node: ``bool``

        :return: ``True`` for success, ``False`` for failure
        :rtype: ``bool``
        """
        # remove the pool member
        destroy_request = ET.Element('removePoolMember',
                                     {'xmlns': TYPES_URN,
                                      'id': member.id})

        result = self.connection.request_with_orgId_api_2(
            action='networkDomainVip/removePoolMember',
            method='POST',
            data=ET.tostring(destroy_request)).object

        if member.node_id is not None and destroy_node is True:
            return self.ex_destroy_node(member.node_id)
        else:
            response_code = findtext(result, 'responseCode', TYPES_URN)
            return response_code in ['IN_PROGRESS', 'OK']

    def ex_get_nodes(self, ex_network_domain_id=None):
        """
        Get the nodes within this geography or in given network.

        :param ex_network_domain_id: UUID of Network Domain
               if not None returns only balancers in the given network
               if None then returns all pools for the organization
        :type  ex_network_domain_id: ``str``

        :return: Returns an ``list`` of ``NttCisVIPNode``
        :rtype: ``list`` of ``NttCisVIPNode``
        """
        params = None
        if ex_network_domain_id is not None:
            params = {"networkDomainId": ex_network_domain_id}

        nodes = self.connection \
            .request_with_orgId_api_2('networkDomainVip/node',
                                      params=params).object
        return self._to_nodes(nodes)

    def ex_get_node(self, node_id):
        """
        Get the node specified by node_id

        :return: Returns an instance of ``NttCisVIPNode``
        :rtype: Instance of ``NttCisVIPNode``
        """
        nodes = self.connection \
            .request_with_orgId_api_2('networkDomainVip/node/%s'
                                      % node_id).object
        return self._to_node(nodes)

    def ex_destroy_node(self, node_id):
        """
        Destroy a specific node

        :param node_id: The ID of of a ``NttCisVIPNode``
        :type  node_id: ``str``

        :return: ``True`` for success, ``False`` for failure
        :rtype: ``bool``
        """
        # Destroy the node
        destroy_request = ET.Element('deleteNode',
                                     {'xmlns': TYPES_URN,
                                      'id': node_id})

        result = self.connection.request_with_orgId_api_2(
            action='networkDomainVip/deleteNode',
            method='POST',
            data=ET.tostring(destroy_request)).object
        response_code = findtext(result, 'responseCode', TYPES_URN)
        return response_code in ['IN_PROGRESS', 'OK']

    def ex_wait_for_state(self, state, func, poll_interval=2,
                          timeout=60, *args, **kwargs):
        """
        Wait for the function which returns a instance
        with field status to match

        Keep polling func until one of the desired states is matched

        :param state: Either the desired state (`str`) or a `list` of states
        :type  state: ``str`` or ``list``

        :param  func: The function to call, e.g. ex_get_vlan
        :type   func: ``function``

        :param  poll_interval: The number of seconds to wait between checks
        :type   poll_interval: `int`

        :param  timeout: The total number of seconds to wait to reach a state
        :type   timeout: `int`

        :param  args: The arguments for func
        :type   args: Positional arguments

        :param  kwargs: The arguments for func
        :type   kwargs: Keyword arguments
        """
        return self.connection.wait_for_state(state, func, poll_interval,
                                              timeout, *args, **kwargs)

    def ex_get_default_health_monitors(self, network_domain):
        """
        Get the default health monitors available for a network domain

        :param network_domain_id: The ID of of a ``NttCisNetworkDomain``
        :type  network_domain_id: ``str``

        :rtype: `list` of :class:`NttCisDefaultHealthMonitor`
        """
        result = self.connection.request_with_orgId_api_2(
            action='networkDomainVip/defaultHealthMonitor',
            params={'networkDomainId': network_domain},
            method='GET').object
        return self._to_health_monitors(result)

    def ex_get_default_persistence_profiles(self, network_domain_id):
        """
        Get the default persistence profiles available for a network domain

        :param network_domain_id: The ID of of a ``NttCisNetworkDomain``
        :type  network_domain_id: ``str``

        :rtype: `list` of :class:`NttCisPersistenceProfile`
        """
        result = self.connection.request_with_orgId_api_2(
            action='networkDomainVip/defaultPersistenceProfile',
            params={'networkDomainId': network_domain_id},
            method='GET').object
        return self._to_persistence_profiles(result)

    def ex_get_default_irules(self, network_domain_id):
        """
        Get the default iRules available for a network domain

        :param network_domain_id: The ID of of a ``NttCisNetworkDomain``
        :type  network_domain_id: ``str``

        :rtype: `list` of :class:`NttCisDefaultiRule`
        """
        result = self.connection.request_with_orgId_api_2(
            action='networkDomainVip/defaultIrule',
            params={'networkDomainId': network_domain_id},
            method='GET').object
        return self._to_irules(result)

    @get_params
    def ex_list_ssl_domain_certs(self, params={}):
        """
        Functions takes a named parameter that can be one or none of the
        following

        :param params: A sequence of comma separated keyword arguments
        and a value
            * id=
            * network_domain_id=
            * name=
            * state=
            * create_time=
            * expiry_time=
        :returns: `list` of :class: `NttCisDomaincertificate`
        """
        result = self.connection.request_with_orgId_api_2(
            action="networkDomainVip/sslDomainCertificate",
            params=params,
            method="GET").object
        return self._to_certs(result)

    def ex_get_ssl_domain_cert(self, cert_id):
        """
        Function gets the cert by id. Use this if only if the id
        is already known

        :param cert_id: The id of the specific cert
        :type cert_id: ``str``
        :returns: :class: `NttCisdomaincertificate
        """
        result = self.connection.request_with_orgId_api_2(
            action="networkDomainVip/sslDomainCertificate/%s" % cert_id,
            method="GET").object
        return self._to_cert(result)

    @get_params
    def ex_list_ssl_certificate_chains(self, params={}):
        """
        Functions takes a named parameter that can be one or none of the
        following to filter returned items

        :param params: A sequence of comma separated keyword arguments
        and a value
            * id=
            * network_domain_id=
            * name=
            * state=
            * create_time=
            * expiry_time=
        :return: `list` of :class: `NttCissslcertficiatechain`
        """
        result = self.connection.request_with_orgId_api_2(
            action="networkDomainVip/sslCertificateChain",
            params=params,
            method="GET").object
        return self._to_certificate_chains(result)

    def ex_get_ssl_certificate_chain(self, chain_id):
        """
        Function gets the certificate chain by id. Use this if only if the id
        is already known
        :param cert_id: The id of the specific cert
        :return: :class: `NttCiscertificatechain
        """
        result = self.connection.request_with_orgId_api_2(
            action="networkDomainVip/sslCertificateChain/%s" % chain_id,
            method="GET").object
        return self._to_certificate_chain(result)

    @get_params
    def ex_list_ssl_offload_profiles(self, params={}):
        """
       Functions takes a named parameter that can be one or none of the
       following to filter returned items

       :param params: A sequence of comma separated keyword arguments
       and a value
           * id=
           * network_domain_id=
           * datacenter_id=
           * name=
           * state=
           * ssl_domain_certificate_id=
           * ssl_domain_certificate_name=
           * ssl_certificate_chain_id=
           * ssl_certificate_chain_name=
           * create_time=
       :return: `list` of :class: `NttCisSslssloffloadprofile`
       """
        result = self.connection.request_with_orgId_api_2(
            action="networkDomainVip/sslOffloadProfile",
            params=params,
            method="GET").object
        return self._to_ssl_profiles(result)

    def ex_get_ssl_offload_profile(self, profile_id):
        result = self.connection.request_with_orgId_api_2(
            action="networkDomainVip/sslOffloadProfile/%s" % profile_id,
            method="GET").object
        return self._to_ssl_profile(result)

    def _to_irules(self, object):
        irules = []
        matches = object.findall(
            fixxpath('defaultIrule', TYPES_URN))
        for element in matches:
            irules.append(self._to_irule(element))
        return irules

    def _to_irule(self, element):
        compatible = []
        matches = element.findall(
            fixxpath('virtualListenerCompatibility', TYPES_URN))
        for match_element in matches:
            compatible.append(
                NttCisVirtualListenerCompatibility(
                    type=match_element.get('type'),
                    protocol=match_element.get('protocol', None)))
        irule_element = element.find(fixxpath('irule', TYPES_URN))
        return NttCisDefaultiRule(
            id=irule_element.get('id'),
            name=irule_element.get('name'),
            compatible_listeners=compatible
        )

    def _to_persistence_profiles(self, object):
        profiles = []
        matches = object.findall(
            fixxpath('defaultPersistenceProfile', TYPES_URN))
        for element in matches:
            profiles.append(self._to_persistence_profile(element))
        return profiles

    def _to_persistence_profile(self, element):
        compatible = []
        matches = element.findall(
            fixxpath('virtualListenerCompatibility', TYPES_URN))
        for match_element in matches:
            compatible.append(
                NttCisVirtualListenerCompatibility(
                    type=match_element.get('type'),
                    protocol=match_element.get('protocol', None)))

        return NttCisPersistenceProfile(
            id=element.get('id'),
            fallback_compatible=bool(
                element.get('fallbackCompatible') == "true"),
            name=findtext(element, 'name', TYPES_URN),
            compatible_listeners=compatible
        )

    def _to_health_monitors(self, object):
        monitors = []
        matches = object.findall(fixxpath('defaultHealthMonitor', TYPES_URN))
        for element in matches:
            monitors.append(self._to_health_monitor(element))
        return monitors

    def _to_health_monitor(self, element):
        return NttCisDefaultHealthMonitor(
            id=element.get('id'),
            name=findtext(element, 'name', TYPES_URN),
            node_compatible=bool(
                findtext(element, 'nodeCompatible', TYPES_URN) == "true"),
            pool_compatible=bool(
                findtext(element, 'poolCompatible', TYPES_URN) == "true"),
        )

    def _to_nodes(self, object):
        nodes = []
        for element in object.findall(fixxpath("node", TYPES_URN)):
            nodes.append(self._to_node(element))

        return nodes

    def _to_node(self, element):
        ipaddress = findtext(element, 'ipv4Address', TYPES_URN)
        if ipaddress is None:
            ipaddress = findtext(element, 'ipv6Address', TYPES_URN)

        name = findtext(element, 'name', TYPES_URN)

        try:
            hm = element.find(fixxpath('healthMonitor', TYPES_URN)).get('id')
        except AttributeError:
            hm = None

        node = NttCisVIPNode(
            id=element.get('id'),
            name=name,
            status=self._VALUE_TO_STATE_MAP.get(
                findtext(element, 'state', TYPES_URN),
                State.UNKNOWN),
            health_monitor=hm,
            connection_rate_limit=findtext(element,
                                           'connectionRateLimit', TYPES_URN),
            connection_limit=findtext(element, 'connectionLimit', TYPES_URN),
            ip=ipaddress)

        return node

    def _to_balancers(self, object):
        loadbalancers = []
        for element in object.findall(fixxpath("virtualListener", TYPES_URN)):
            loadbalancers.append(self._to_balancer(element))

        return loadbalancers

    def _to_balancer(self, element):
        ipaddress = findtext(element, 'listenerIpAddress', TYPES_URN)
        name = findtext(element, 'name', TYPES_URN)
        port = findtext(element, 'port', TYPES_URN)
        extra = {}

        pool_element = element.find(fixxpath(
            'pool',
            TYPES_URN))
        if pool_element is None:
            extra['pool_id'] = None

        else:
            extra['pool_id'] = pool_element.get('id')

        extra['network_domain_id'] = findtext(element, 'networkDomainId',
                                              TYPES_URN)

        balancer = LoadBalancer(
            id=element.get('id'),
            name=name,
            state=self._VALUE_TO_STATE_MAP.get(
                findtext(element, 'state', TYPES_URN),
                State.UNKNOWN),
            ip=ipaddress,
            port=port,
            driver=self.connection.driver,
            extra=extra
        )

        return balancer

    def _to_members(self, object):
        members = []
        for element in object.findall(fixxpath("poolMember", TYPES_URN)):
            members.append(self._to_member(element))

        return members

    def _to_member(self, element):
        port = findtext(element, 'port', TYPES_URN)
        if port is not None:
            port = int(port)
        pool_member = NttCisPoolMember(
            id=element.get('id'),
            name=element.find(fixxpath(
                'node',
                TYPES_URN)).get('name'),
            status=findtext(element, 'state', TYPES_URN),
            node_id=element.find(fixxpath(
                'node',
                TYPES_URN)).get('id'),
            ip=element.find(fixxpath(
                'node',
                TYPES_URN)).get('ipAddress'),
            port=port
        )
        return pool_member

    def _to_pools(self, object):
        pools = []
        for element in object.findall(fixxpath("pool", TYPES_URN)):
            pools.append(self._to_pool(element))

        return pools

    def _to_pool(self, element):
        pool = NttCisPool(
            id=element.get('id'),
            name=findtext(element, 'name', TYPES_URN),
            status=findtext(element, 'state', TYPES_URN),
            description=findtext(element, 'description', TYPES_URN),
            load_balance_method=findtext(element, 'loadBalanceMethod',
                                         TYPES_URN),
            health_monitor_id=findtext(element, 'healthMonitorId', TYPES_URN),
            service_down_action=findtext(element, 'serviceDownAction',
                                         TYPES_URN),
            slow_ramp_time=findtext(element, 'slowRampTime', TYPES_URN),
        )
        return pool

    def _to_certs(self, object):
        certs = []
        for element in object.findall(fixxpath("sslDomainCertificate",
                                               TYPES_URN)):
            certs.append(self._to_cert(element))
        return certs

    def _to_cert(self, el):
        return process_xml(ET.tostring(el))

    def _to_certificate_chains(self, object):
        cert_chains = []
        for element in object.findall(fixxpath("sslCertificateChain",
                                               TYPES_URN)):
            cert_chains.append(self._to_certificate_chain(element))
        return cert_chains

    def _to_certificate_chain(self, el):
        return process_xml(ET.tostring(el))

    def _to_ssl_profiles(self, object):
        profiles = []
        for element in object.findall(fixxpath("sslOffloadProfile",
                                               TYPES_URN)):
            profiles.append(self._to_ssl_profile(element))
        return profiles

    def _to_ssl_profile(self, el):
        return process_xml(ET.tostring(el))
