Ejemplo n.º 1
0
from libcloud.common.types import InvalidCredsError, LibcloudError
from libcloud.dns.base import DNSDriver, Zone, Record
from libcloud.dns.types import Provider, RecordType
from libcloud.dns.types import RecordAlreadyExistsError, ZoneAlreadyExistsError
from libcloud.dns.types import RecordDoesNotExistError, ZoneDoesNotExistError
from libcloud.utils.misc import merge_valid_keys, reverse_dict

API_HOST = 'api.cloudflare.com'
API_BASE = '/client/v4'

CLOUDFLARE_TO_LIBCLOUD_ZONE_TYPE = {
    'full': 'master',
    'partial': 'slave',
}

LIBCLOUD_TO_CLOUDFLARE_ZONE_TYPE = reverse_dict(
    CLOUDFLARE_TO_LIBCLOUD_ZONE_TYPE)

ZONE_EXTRA_ATTRIBUTES = {
    'development_mode',
    'original_name_servers',
    'original_registrar',
    'original_dnshost',
    'created_on',
    'modified_on',
    'activated_on',
    'owner',
    'account',
    'permissions',
    'plan',
    'plan_pending',
    'status',
Ejemplo n.º 2
0
class GoGridLBDriver(BaseGoGridDriver, Driver):
    connectionCls = GoGridLBConnection
    api_name = 'gogrid_lb'
    name = 'GoGrid LB'

    LB_STATE_MAP = {'On': State.RUNNING, 'Unknown': State.UNKNOWN}
    _VALUE_TO_ALGORITHM_MAP = {
        'round robin': Algorithm.ROUND_ROBIN,
        'least connect': Algorithm.LEAST_CONNECTIONS
    }
    _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)

    def list_protocols(self):
        # GoGrid only supports http
        return ['http']

    def list_balancers(self):
        return self._to_balancers(
            self.connection.request('/api/grid/loadbalancer/list').object)

    def ex_create_balancer_nowait(self,
                                  name,
                                  members,
                                  protocol='http',
                                  port=80,
                                  algorithm=DEFAULT_ALGORITHM):
        algorithm = self._algorithm_to_value(algorithm)

        params = {
            'name': name,
            'loadbalancer.type': algorithm,
            'virtualip.ip': self._get_first_ip(),
            'virtualip.port': port
        }
        params.update(self._members_to_params(members))

        resp = self.connection.request('/api/grid/loadbalancer/add',
                                       method='GET',
                                       params=params)
        return self._to_balancers(resp.object)[0]

    def create_balancer(self,
                        name,
                        members,
                        protocol='http',
                        port=80,
                        algorithm=DEFAULT_ALGORITHM):
        balancer = self.ex_create_balancer_nowait(name, members, protocol,
                                                  port, algorithm)

        timeout = 60 * 20
        waittime = 0
        interval = 2 * 15

        if balancer.id is not None:
            return balancer
        else:
            while waittime < timeout:
                balancers = self.list_balancers()

                for i in balancers:
                    if i.name == balancer.name and i.id is not None:
                        return i

                waittime += interval
                time.sleep(interval)

        raise Exception('Failed to get id')

    def destroy_balancer(self, balancer):
        try:
            resp = self.connection.request('/api/grid/loadbalancer/delete',
                                           method='POST',
                                           params={'id': balancer.id})
        except Exception:
            e = sys.exc_info()[1]
            if "Update request for LoadBalancer" in str(e):
                raise LibcloudLBImmutableError(
                    "Cannot delete immutable object", GoGridLBDriver)
            else:
                raise

        return resp.status == 200

    def get_balancer(self, **kwargs):
        params = {}

        try:
            params['name'] = kwargs['ex_balancer_name']
        except KeyError:
            balancer_id = kwargs['balancer_id']
            params['id'] = balancer_id

        resp = self.connection.request('/api/grid/loadbalancer/get',
                                       params=params)

        return self._to_balancers(resp.object)[0]

    def balancer_attach_member(self, balancer, member):
        members = self.balancer_list_members(balancer)
        members.append(member)

        params = {"id": balancer.id}

        params.update(self._members_to_params(members))

        resp = self._update_balancer(params)

        return [
            m for m in self._to_members(resp.object["list"][0]["realiplist"])
            if m.ip == member.ip
        ][0]

    def balancer_detach_member(self, balancer, member):
        members = self.balancer_list_members(balancer)

        remaining_members = [n for n in members if n.id != member.id]

        params = {"id": balancer.id}
        params.update(self._members_to_params(remaining_members))

        resp = self._update_balancer(params)

        return resp.status == 200

    def balancer_list_members(self, balancer):
        resp = self.connection.request('/api/grid/loadbalancer/get',
                                       params={'id': balancer.id})
        return self._to_members(resp.object["list"][0]["realiplist"])

    def _update_balancer(self, params):
        try:
            return self.connection.request('/api/grid/loadbalancer/edit',
                                           method='POST',
                                           params=params)
        except Exception:
            e = sys.exc_info()[1]
            if "Update already pending" in str(e):
                raise LibcloudLBImmutableError("Balancer is immutable",
                                               GoGridLBDriver)

        raise LibcloudError(value='Exception: %s' % str(err), driver=self)

    def _members_to_params(self, members):
        """
        Helper method to convert list of L{Member} objects
        to GET params.

        """

        params = {}

        i = 0
        for member in members:
            params["realiplist.%s.ip" % i] = member.ip
            params["realiplist.%s.port" % i] = member.port
            i += 1

        return params

    def _to_balancers(self, object):
        return [self._to_balancer(el) for el in object["list"]]

    def _to_balancer(self, el):
        lb = LoadBalancer(id=el.get("id"),
                          name=el["name"],
                          state=self.LB_STATE_MAP.get(el["state"]["name"],
                                                      State.UNKNOWN),
                          ip=el["virtualip"]["ip"]["ip"],
                          port=el["virtualip"]["port"],
                          driver=self.connection.driver)
        return lb

    def _to_members(self, object):
        return [self._to_member(el) for el in object]

    def _to_member(self, el):
        member = Member(id=el["ip"]["id"], ip=el["ip"]["ip"], port=el["port"])
        return member
Ejemplo n.º 3
0
class SoftlayerLBDriver(Driver):
    name = 'Softlayer Load Balancing'
    website = 'http://www.softlayer.com/'
    connectionCls = SoftLayerConnection

    _VALUE_TO_ALGORITHM_MAP = {
        'ROUND_ROBIN': Algorithm.ROUND_ROBIN,
        'LEAST_CONNECTIONS': Algorithm.LEAST_CONNECTIONS,
        'SHORTEST_RESPONSE': Algorithm.SHORTEST_RESPONSE,
        'PERSISTENT_IP': Algorithm.PERSISTENT_IP
    }

    _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)

    def list_balancers(self):
        mask = {
            'adcLoadBalancers': {
                'ipAddress': '',
                'loadBalancerHardware': {
                    'datacenter': ''
                },
                'virtualServers': {
                    'serviceGroups': {
                        'routingMethod': '',
                        'routingType': '',
                        'services': {
                            'ipAddress': ''
                        }
                    }
                }
            }
        }

        res = self.connection.request(
            'SoftLayer_Account', 'getAdcLoadBalancers',
            object_mask=mask).object

        return [self._to_balancer(lb) for lb in res]

    def get_balancer(self, balancer_id):
        balancers = self.list_balancers()
        balancer = find(balancers, lambda b: b.id == balancer_id)
        if not balancer:
            raise LibcloudError(value='No balancer found for id: %s' %
                                balancer_id, driver=self)
        return balancer

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

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

    def balancer_list_members(self, balancer):
        lb = self._get_balancer_model(balancer.id)
        members = []
        vs = self._locate_service_group(lb, balancer.port)
        if vs:
            if vs['serviceGroups']:
                srvgrp = vs['serviceGroups'][0]
                members = [self._to_member(srv, balancer) for
                           srv in srvgrp['services']]

        return members

    def balancer_attach_member(self, balancer, member):
        lb = self._get_balancer_model(balancer.id)
        vs = self._locate_service_group(lb, balancer.port)
        if not vs:
            raise LibcloudError(value='No service_group found for balancer '
                                'port: %s' % balancer.port, driver=self)

        if vs['serviceGroups']:
            services = vs['serviceGroups'][0]['services']
            services.append(self._to_service_template(member.ip,
                                                      member.port))

        self.connection.request(lb_service, 'editObject', lb, id=balancer.id)

        return [m for m in balancer.list_members() if m.ip == member.ip][0]

    def balancer_detach_member(self, balancer, member):
        svc_lbsrv = 'SoftLayer_Network_Application_Delivery_Controller_'\
            'LoadBalancer_Service'

        self.connection.request(svc_lbsrv, 'deleteObject', id=member.id)
        return True

    def destroy_balancer(self, balancer):
        res_billing = self.connection.request(lb_service, 'getBillingItem',
                                              id=balancer.id).object

        self.connection.request('SoftLayer_Billing_Item', 'cancelService',
                                id=res_billing['id'])
        return True

    def ex_list_balancer_packages(self):
        """
        Retrieves the available local load balancer packages.

        :rtype: ``list`` of :class:`LBPackage`
        """
        mask = {
            'prices': ''
        }
        res = self.connection.request('SoftLayer_Product_Package', 'getItems',
                                      id=0, object_mask=mask).object

        res_lb_pkgs = [r for r in res if r['description'].find
                       ('Load Balancer') != -1]
        res_lb_pkgs = [r for r in res_lb_pkgs if not r['description'].
                       startswith('Global')]

        return [self._to_lb_package(r) for r in res_lb_pkgs]

    def ex_place_balancer_order(self, package, location):
        """
        Places an order for a local loadbalancer in the specified
        location.

        :param package: The package to create the loadbalancer from.
        :type package: :class:`LBPackage`

        :param string location: The location (datacenter) to create the
                                loadbalancer.
        :type location: :class:`NodeLocation`

        :return: ``True`` if ex_place_balancer_order was successful.
        :rtype: ``bool``
        """
        data = {
            'complexType': 'SoftLayer_Container_Product_Order_Network_'
                           'LoadBalancer',
            'quantity': 1,
            'packageId': 0,
            'location': self._get_location(location.id),
            'prices': [{'id': package.price_id}]
        }

        self.connection.request('SoftLayer_Product_Order', 'placeOrder',
                                data)
        return True

    def ex_configure_load_balancer(self, balancer, port=80,
                                   protocol='http',
                                   algorithm=DEFAULT_ALGORITHM,
                                   ex_allocation=100):
        """
        Configure the loadbalancer by adding it with a front-end port (aka
        a service group in the Softlayer loadbalancer model).

        Softlayer loadbalancer may be defined with multiple service
        groups (front-end ports) each defined with a unique port number.

        :param balancer: The loadbalancer.
        :type  balancer: :class:`LoadBalancer`

        :param port: Port of the service group, defaults to 80.
        :type  port: ``int``

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

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

        :param ex_allocation: The percentage of the total connection
                              allocations to allocate for this group.
        :type  ex_allocation: ``int``

        :return: ``True`` if ex_add_service_group was successful.
        :rtype: ``bool``
        """
        _types = self._get_routing_types()
        _methods = self._get_routing_methods()

        rt = find(_types, lambda t: t['keyname'] == protocol.upper())
        if not rt:
            raise LibcloudError(value='Invalid protocol %s' % protocol,
                                driver=self)

        value = self._algorithm_to_value(algorithm)
        meth = find(_methods, lambda m: m['keyname'] == value)
        if not meth:
            raise LibcloudError(value='Invalid algorithm %s' % algorithm,
                                driver=self)

        service_group_template = {
            'port': port,
            'allocation': ex_allocation,
            'serviceGroups': [{
                'routingTypeId': rt['id'],
                'routingMethodId': meth['id']
            }]
        }

        lb = self._get_balancer_model(balancer.id)
        if len(lb['virtualServers']) > 0:
            port = lb['virtualServers'][0]['port']
            raise LibcloudError(value='Loadbalancer already configured with '
                                'a service group (front-end port)' % port,
                                driver=self)

        lb['virtualServers'].append(service_group_template)
        self.connection.request(lb_service, 'editObject', lb, id=balancer.id)
        return True

    def _get_balancer_model(self, balancer_id):
        """
        Retrieve Softlayer loadbalancer model.
        """
        lb_mask = {
            'virtualServers': {
                'serviceGroups': {
                    'services': {
                        'ipAddress': '',
                        'groupReferences': '',
                    }
                }
            }
        }

        lb_res = self.connection.request(lb_service, 'getObject',
                                         object_mask=lb_mask, id=balancer_id).\
            object
        return lb_res

    def _locate_service_group(self, lb, port):
        """
        Locate service group with given port.

        Return virtualServers (vs) entry whose port matches the
        supplied parameter port. For a negative port, just return
        the first vs entry.
        None is returned if no match found.

        :param lb: Softlayer loadbalancer model.
        :type lb: ``dict``

        :param port: loadbalancer front-end port.
        :type port: ``int``

        :return: Matched entry in the virtualServers array of the supplied
        model.
        :rtype: ``dict``
        """
        vs = None
        if port < 0:
            vs = lb['virtualServers'][0] if lb['virtualServers']\
                else None
        else:
            vs = find(lb['virtualServers'], lambda v: v['port'] == port)

        return vs

    def _get_routing_types(self):
        svc_rtype = 'SoftLayer_Network_Application_Delivery_Controller_'\
            'LoadBalancer_Routing_Type'

        return self.connection.request(svc_rtype, 'getAllObjects').object

    def _get_routing_methods(self):
        svc_rmeth = 'SoftLayer_Network_Application_Delivery_Controller_'\
            'LoadBalancer_Routing_Method'

        return self.connection.request(svc_rmeth, 'getAllObjects').object

    def _get_location(self, location_id):
        res = self.connection.request('SoftLayer_Location_Datacenter',
                                      'getDatacenters').object

        dcenter = find(res, lambda d: d['name'] == location_id)
        if not dcenter:
            raise LibcloudError(value='Invalid value %s' % location_id,
                                driver=self)
        return dcenter['id']

    def _get_ipaddress(self, ip):
        svc_ipaddress = 'SoftLayer_Network_Subnet_IpAddress'

        return self.connection.request(svc_ipaddress, 'getByIpAddress',
                                       ip).object

    def _to_lb_package(self, pkg):
        try:
            price_id = pkg['prices'][0]['id']
        except Exception:
            price_id = -1

        capacity = int(pkg.get('capacity', 0))
        return LBPackage(id=pkg['id'], name=pkg['keyName'],
                         description=pkg['description'],
                         price_id=price_id, capacity=capacity)

    def _to_service_template(self, ip, port):
        """
        Builds single member entry in Softlayer loadbalancer model
        """
        template = {
            'enabled': 1,  # enable the service
            'port': port,  # back-end port
            'ipAddressId': self._get_ipaddress(ip)['id'],
            'healthChecks': [{
                'healthCheckTypeId': 21  # default health check
            }],
            'groupReferences': [{
                'weight': 1
            }]
        }

        return template

    def _to_balancer(self, lb):
        ipaddress = lb['ipAddress']['ipAddress']

        extra = {}
        extra['connection_limit'] = lb['connectionLimit']
        extra['ssl_active'] = lb['sslActiveFlag']
        extra['ssl_enabled'] = lb['sslEnabledFlag']
        extra['ha'] = lb['highAvailabilityFlag']
        extra['datacenter'] = \
            lb['loadBalancerHardware'][0]['datacenter']['name']

        # In Softlayer, there could be multiple group of members (aka service
        # groups), so retrieve the first one
        vs = self._locate_service_group(lb, -1)
        if vs:
            port = vs['port']
            if vs['serviceGroups']:
                srvgrp = vs['serviceGroups'][0]
                routing_method = srvgrp['routingMethod']['keyname']
                routing_type = srvgrp['routingType']['keyname']
                try:
                    extra['algorithm'] = self.\
                        _value_to_algorithm(routing_method)
                except Exception:
                    pass
                extra['protocol'] = routing_type.lower()

        if not vs:
            port = -1

        balancer = LoadBalancer(
            id=lb['id'],
            name='',
            state=State.UNKNOWN,
            ip=ipaddress,
            port=port,
            driver=self.connection.driver,
            extra=extra
        )

        return balancer

    def _to_member(self, srv, balancer=None):
        svc_id = srv['id']
        ip = srv['ipAddress']['ipAddress']
        port = srv['port']

        extra = {}
        extra['status'] = srv['status']
        extra['enabled'] = srv['enabled']
        return Member(id=svc_id, ip=ip, port=port, balancer=balancer,
                      extra=extra)
Ejemplo n.º 4
0
class DimensionDataLBDriver(Driver):
    """
    DimensionData node driver.
    """

    selected_region = None
    connectionCls = DimensionDataConnection
    name = 'Dimension Data Load Balancer'
    website = 'https://cloud.dimensiondata.com/'
    type = Provider.DIMENSIONDATA
    api_version = 1.0

    network_domain_id = None

    _VALUE_TO_ALGORITHM_MAP = {
        'ROUND_ROBIN': Algorithm.ROUND_ROBIN,
        'LEAST_CONNECTIONS': Algorithm.LEAST_CONNECTIONS,
        'SHORTEST_RESPONSE': Algorithm.SHORTEST_RESPONSE,
        'PERSISTENT_IP': Algorithm.PERSISTENT_IP
    }
    _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,
                 secret=None,
                 secure=True,
                 host=None,
                 port=None,
                 api_version=None,
                 region=DEFAULT_REGION,
                 **kwargs):

        if region not in API_ENDPOINTS:
            raise ValueError('Invalid region: %s' % (region))

        self.selected_region = API_ENDPOINTS[region]

        super(DimensionDataLBDriver, 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(DimensionDataLBDriver,
                       self)._ex_connection_class_kwargs()
        kwargs['region'] = self.selected_region
        return kwargs

    def create_balancer(self, name, port, protocol, algorithm, members):
        """
        Create a new load balancer instance

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

        :param port: Port the load balancer should listen on,
                    defaults to 80 (required)
        :type  port: ``str``

        :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`

        :rtype: :class:`LoadBalancer`
        """
        network_domain_id = self.network_domain_id
        if port is None:
            port = 80
        if protocol is None:
            protocol = 'http'
        if algorithm is None:
            algorithm = Algorithm.ROUND_ROBIN

        # 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:
                node = self.ex_create_node(network_domain_id=network_domain_id,
                                           name=member.ip,
                                           ip=member.ip,
                                           ex_description=None)
                self.ex_create_pool_member(pool=pool, node=node, 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=port,
            pool=pool)

        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
                            })

    def list_balancers(self):
        """
        List all loadbalancers inside a geography.

        In Dimension Data terminology these are known as virtual listeners

        :rtype: ``list`` of :class:`LoadBalancer`
        """

        return self._to_balancers(
            self.connection.request_with_orgId_api_2(
                'networkDomainVip/virtualListener').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 Dimension Data, this is a list
        of common protocols.

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

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

        In Dimension Data 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 ``DimensionDataPool`` (required)
        :type  pool: ``DimensionDataPool``

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

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

        :return: The node member, instance of ``DimensionDataPoolMember``
        :rtype: ``DimensionDataPoolMember``
        """
        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 DimensionDataPoolMember(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,
                       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 ``DimensionDataVIPNode``
        :rtype: ``DimensionDataVIPNode``
        """
        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
        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 DimensionDataVIPNode(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 ``DimensionDataNode`` to update
        :type  pool: ``DimensionDataNode``

        :return: The instance of ``DimensionDataNode``
        :rtype: ``DimensionDataNode``
        """
        create_node_elm = ET.Element('editNode', {'xmlns': TYPES_URN})
        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/createNode',
            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 ``DimensionDataNode`` to update
        :type  pool: ``DimensionDataNode``

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

        :return: The instance of ``DimensionDataNode``
        :rtype: ``DimensionDataNode``
        """
        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:`DimensionDataDefaultHealthMonitor`

        :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 ``DimensionDataPool``
        :rtype: ``DimensionDataPool``
        """
        # 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 DimensionDataPool(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,
                                   pool,
                                   listener_ip_address=None,
                                   persistence_profile=None,
                                   fallback_persistence_profile=None,
                                   irule=None,
                                   protocol='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: Description of the node (required)
        :type  port: ``str``

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

        :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:`DimensionDataPersistenceProfile`

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

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

        :param protocol: For STANDARD type, ANY, TCP or UDP
                         for PERFORMANCE_LAYER_4 choice of ANY, TCP, UDP, HTTP
        :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: ``DimensionDataVirtualListener``
        """
        if port is 80 or 443:
            listener_type = 'PERFORMANCE_LAYER_4'
            protocol = 'HTTP'
        else:
            listener_type = 'STANDARD'

        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)
        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
        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 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 DimensionDataVirtualListener(id=virtual_listener_id,
                                            name=name,
                                            ip=virtual_listener_ip,
                                            status=State.RUNNING)

    def ex_get_pools(self):
        """
        Get all of the pools inside the current geography

        :return: Returns a ``list`` of type ``DimensionDataPool``
        :rtype: ``list`` of ``DimensionDataPool``
        """
        pools = self.connection \
            .request_with_orgId_api_2('networkDomainVip/pool').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 ``DimensionDataPool``
        :rtype: ``DimensionDataPool``
        """
        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 ``DimensionDataPool`` to update
        :type  pool: ``DimensionDataPool``

        :return: ``True`` for success, ``False`` for failure
        :rtype: ``bool``
        """
        create_node_elm = ET.Element('editPool', {'xmlns': TYPES_URN})

        ET.SubElement(create_node_elm, "loadBalanceMethod") \
            .text = str(pool.load_balance_method)
        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 ``DimensionDataPool`` to destroy
        :type  pool: ``DimensionDataPool``

        :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: ``DimensionDataPool``

        :return: Returns an ``list`` of ``DimensionDataPoolMember``
        :rtype: ``list`` of ``DimensionDataPoolMember``
        """
        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 ``DimensionDataPoolMember``
        :rtype: ``DimensionDataPoolMember``
        """
        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: ``DimensionDataPoolMember``

        :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):
        """
        Get the nodes within this geography

        :return: Returns an ``list`` of ``DimensionDataVIPNode``
        :rtype: ``list`` of ``DimensionDataVIPNode``
        """
        nodes = self.connection \
            .request_with_orgId_api_2('networkDomainVip/node').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 ``DimensionDataVIPNode``
        :rtype: Instance of ``DimensionDataVIPNode``
        """
        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 ``DimensionDataVIPNode``
        :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_id):
        """
        Get the default health monitors available for a network domain

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

        :rtype: `list` of :class:`DimensionDataDefaultHealthMonitor`
        """
        result = self.connection.request_with_orgId_api_2(
            action='networkDomainVip/defaultHealthMonitor',
            params={
                'networkDomainId': network_domain_id
            },
            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 ``DimensionDataNetworkDomain``
        :type  network_domain_id: ``str``

        :rtype: `list` of :class:`DimensionDataPersistenceProfile`
        """
        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 ``DimensionDataNetworkDomain``
        :type  network_domain_id: ``str``

        :rtype: `list` of :class:`DimensionDataDefaultiRule`
        """
        result = self.connection.request_with_orgId_api_2(
            action='networkDomainVip/defaultIrule',
            params={
                'networkDomainId': network_domain_id
            },
            method='GET').object
        return self._to_irules(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(
                DimensionDataVirtualListenerCompatibility(
                    type=match_element.get('type'),
                    protocol=match_element.get('protocol', None)))
        irule_element = element.find(fixxpath('irule', TYPES_URN))
        return DimensionDataDefaultiRule(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(
                DimensionDataVirtualListenerCompatibility(
                    type=match_element.get('type'),
                    protocol=match_element.get('protocol', None)))

        return DimensionDataPersistenceProfile(
            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 DimensionDataDefaultHealthMonitor(
            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)

        node = DimensionDataVIPNode(
            id=element.get('id'),
            name=name,
            status=self._VALUE_TO_STATE_MAP.get(
                findtext(element, 'state', TYPES_URN), State.UNKNOWN),
            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 = DimensionDataPoolMember(
            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 = DimensionDataPool(
            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
Ejemplo n.º 5
0
class CloudStackLBDriver(CloudStackDriverMixIn, Driver):
    """Driver for CloudStack load balancers."""

    api_name = 'cloudstack_lb'

    _VALUE_TO_ALGORITHM_MAP = {
        'roundrobin': Algorithm.ROUND_ROBIN,
        'leastconn': Algorithm.LEAST_CONNECTIONS
    }
    _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)

    LB_STATE_MAP = {
        'Active': State.RUNNING,
    }

    def list_protocols(self):
        """We don't actually have any protocol awareness beyond TCP."""
        return ['tcp']

    def list_balancers(self):
        balancers = self._sync_request('listLoadBalancerRules')
        balancers = balancers.get('loadbalancerrule', [])
        return [self._to_balancer(balancer) for balancer in balancers]

    def get_balancer(self, balancer_id):
        balancer = self._sync_request('listLoadBalancerRules', id=balancer_id)
        balancer = balancer.get('loadbalancerrule', [])
        if not balancer:
            raise Exception("no such load balancer: " + str(balancer_id))
        return self._to_balancer(balancer[0])

    def create_balancer(self,
                        name,
                        members,
                        protocol='http',
                        port=80,
                        algorithm=DEFAULT_ALGORITHM,
                        location=None,
                        private_port=None):
        if location is None:
            locations = self._sync_request('listZones')
            location = locations['zone'][0]['id']
        else:
            location = location.id
        if private_port is None:
            private_port = port

        result = self._async_request('associateIpAddress', zoneid=location)
        public_ip = result['ipaddress']

        result = self._sync_request(
            'createLoadBalancerRule',
            algorithm=self._ALGORITHM_TO_VALUE_MAP[algorithm],
            name=name,
            privateport=private_port,
            publicport=port,
            publicipid=public_ip['id'],
        )

        balancer = self._to_balancer(result['loadbalancer'])

        for member in members:
            balancer.attach_member(member)

        return balancer

    def destroy_balancer(self, balancer):
        self._async_request('deleteLoadBalancerRule', id=balancer.id)
        self._async_request('disassociateIpAddress',
                            id=balancer.ex_public_ip_id)

    def balancer_attach_member(self, balancer, member):
        member.port = balancer.ex_private_port
        self._async_request('assignToLoadBalancerRule',
                            id=balancer.id,
                            virtualmachineids=member.id)
        return True

    def balancer_detach_member(self, balancer, member):
        self._async_request('removeFromLoadBalancerRule',
                            id=balancer.id,
                            virtualmachineids=member.id)
        return True

    def balancer_list_members(self, balancer):
        members = self._sync_request('listLoadBalancerRuleInstances',
                                     id=balancer.id)
        members = members['loadbalancerruleinstance']
        return [self._to_member(m, balancer.ex_private_port) for m in members]

    def _to_balancer(self, obj):
        balancer = LoadBalancer(id=obj['id'],
                                name=obj['name'],
                                state=self.LB_STATE_MAP.get(
                                    obj['state'], State.UNKNOWN),
                                ip=obj['publicip'],
                                port=obj['publicport'],
                                driver=self.connection.driver)
        balancer.ex_private_port = obj['privateport']
        balancer.ex_public_ip_id = obj['publicipid']
        return balancer

    def _to_member(self, obj, port):
        return Member(id=obj['id'], ip=obj['nic'][0]['ipaddress'], port=port)
Ejemplo n.º 6
0
class CloudStackLBDriver(CloudStackDriverMixIn, Driver):
    """Driver for CloudStack load balancers."""

    api_name = 'cloudstack_lb'
    name = 'CloudStack'
    website = 'http://cloudstack.org/'
    type = Provider.CLOUDSTACK

    _VALUE_TO_ALGORITHM_MAP = {
        'roundrobin': Algorithm.ROUND_ROBIN,
        'leastconn': Algorithm.LEAST_CONNECTIONS
    }
    _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)

    LB_STATE_MAP = {
        'Active': State.RUNNING,
    }

    def __init__(self, key, secret=None, secure=True, host=None,
                 path=None, port=None, *args, **kwargs):
        """
        @inherits: :class:`Driver.__init__`
        """
        host = host if host else self.host
        path = path if path else self.path

        if path is not None:
            self.path = path

        if host is not None:
            self.host = host

        if (self.type == Provider.CLOUDSTACK) and (not host or not path):
            raise Exception('When instantiating CloudStack driver directly ' +
                            'you also need to provide host and path argument')

        super(CloudStackLBDriver, self).__init__(key=key, secret=secret,
                                                 secure=secure,
                                                 host=host, port=port)

    def list_protocols(self):
        """
        We don't actually have any protocol awareness beyond TCP.

        :rtype: ``list`` of ``str``
        """
        return ['tcp']

    def list_balancers(self):
        balancers = self._sync_request('listLoadBalancerRules')
        balancers = balancers.get('loadbalancerrule', [])
        return [self._to_balancer(balancer) for balancer in balancers]

    def get_balancer(self, balancer_id):
        balancer = self._sync_request('listLoadBalancerRules', id=balancer_id)
        balancer = balancer.get('loadbalancerrule', [])
        if not balancer:
            raise Exception("no such load balancer: " + str(balancer_id))
        return self._to_balancer(balancer[0])

    def create_balancer(self, name, members, protocol='http', port=80,
                        algorithm=DEFAULT_ALGORITHM, location=None,
                        private_port=None):
        """
        @inherits: :class:`Driver.create_balancer`

        :param location: Location
        :type  location: :class:`NodeLocation`

        :param private_port: Private port
        :type  private_port: ``int``
        """
        if location is None:
            locations = self._sync_request('listZones')
            location = locations['zone'][0]['id']
        else:
            location = location.id
        if private_port is None:
            private_port = port

        result = self._async_request('associateIpAddress', zoneid=location)
        public_ip = result['ipaddress']

        result = self._sync_request(
            'createLoadBalancerRule',
            algorithm=self._ALGORITHM_TO_VALUE_MAP[algorithm],
            name=name,
            privateport=private_port,
            publicport=port,
            publicipid=public_ip['id'],
        )

        balancer = self._to_balancer(result['loadbalancer'])

        for member in members:
            balancer.attach_member(member)

        return balancer

    def destroy_balancer(self, balancer):
        self._async_request('deleteLoadBalancerRule', id=balancer.id)
        self._async_request('disassociateIpAddress',
                            id=balancer.ex_public_ip_id)

    def balancer_attach_member(self, balancer, member):
        member.port = balancer.ex_private_port
        self._async_request('assignToLoadBalancerRule', id=balancer.id,
                            virtualmachineids=member.id)
        return True

    def balancer_detach_member(self, balancer, member):
        self._async_request('removeFromLoadBalancerRule', id=balancer.id,
                            virtualmachineids=member.id)
        return True

    def balancer_list_members(self, balancer):
        members = self._sync_request('listLoadBalancerRuleInstances',
                                     id=balancer.id)
        members = members['loadbalancerruleinstance']
        return [self._to_member(m, balancer.ex_private_port, balancer)
                for m in members]

    def _to_balancer(self, obj):
        balancer = LoadBalancer(
            id=obj['id'],
            name=obj['name'],
            state=self.LB_STATE_MAP.get(obj['state'], State.UNKNOWN),
            ip=obj['publicip'],
            port=obj['publicport'],
            driver=self.connection.driver
        )
        balancer.ex_private_port = obj['privateport']
        balancer.ex_public_ip_id = obj['publicipid']
        return balancer

    def _to_member(self, obj, port, balancer):
        return Member(
            id=obj['id'],
            ip=obj['nic'][0]['ipaddress'],
            port=port,
            balancer=balancer
        )
Ejemplo n.º 7
0
class RackspaceLBDriver(Driver):
    connectionCls = RackspaceConnection
    api_name = 'rackspace_lb'
    name = 'Rackspace LB'

    LB_STATE_MAP = {'ACTIVE': State.RUNNING, 'BUILD': State.PENDING}
    _VALUE_TO_ALGORITHM_MAP = {
        'RANDOM': Algorithm.RANDOM,
        'ROUND_ROBIN': Algorithm.ROUND_ROBIN,
        'LEAST_CONNECTIONS': Algorithm.LEAST_CONNECTIONS
    }
    _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)

    def list_protocols(self):
        return self._to_protocols(
            self.connection.request('/loadbalancers/protocols').object)

    def list_balancers(self, ex_member_address=None):
        """
        @param ex_member_address: Optional IP address of the attachment member.
                                  If provided, only the load balancers which
                                  have this member attached will be returned.
        @type ex_member_address: C{str}
        """
        params = {}

        if ex_member_address:
            params['nodeaddress'] = ex_member_address

        return self._to_balancers(
            self.connection.request('/loadbalancers', params=params).object)

    def create_balancer(self,
                        name,
                        members,
                        protocol='http',
                        port=80,
                        algorithm=DEFAULT_ALGORITHM):
        algorithm = self._algorithm_to_value(algorithm)

        balancer_object = {
            "loadBalancer": {
                "name":
                name,
                "port":
                port,
                "algorithm":
                algorithm,
                "protocol":
                protocol.upper(),
                "virtualIps": [{
                    "type": "PUBLIC"
                }],
                "nodes": [{
                    "address": member.ip,
                    "port": member.port,
                    "condition": "ENABLED"
                } for member in members],
            }
        }

        resp = self.connection.request('/loadbalancers',
                                       method='POST',
                                       data=json.dumps(balancer_object))
        return self._to_balancer(resp.object["loadBalancer"])

    def destroy_balancer(self, balancer):
        uri = '/loadbalancers/%s' % (balancer.id)
        resp = self.connection.request(uri, method='DELETE')

        return resp.status == 202

    def get_balancer(self, balancer_id):
        uri = '/loadbalancers/%s' % (balancer_id)
        resp = self.connection.request(uri)

        return self._to_balancer(resp.object["loadBalancer"])

    def balancer_attach_member(self, balancer, member):
        ip = member.ip
        port = member.port

        member_object = {
            "nodes": [{
                "port": port,
                "address": ip,
                "condition": "ENABLED"
            }]
        }

        uri = '/loadbalancers/%s/nodes' % (balancer.id)
        resp = self.connection.request(uri,
                                       method='POST',
                                       data=json.dumps(member_object))
        return self._to_members(resp.object)[0]

    def balancer_detach_member(self, balancer, member):
        # Loadbalancer always needs to have at least 1 member.
        # Last member cannot be detached. You can only disable it or destroy the
        # balancer.
        uri = '/loadbalancers/%s/nodes/%s' % (balancer.id, member.id)
        resp = self.connection.request(uri, method='DELETE')

        return resp.status == 202

    def balancer_list_members(self, balancer):
        uri = '/loadbalancers/%s/nodes' % (balancer.id)
        return self._to_members(self.connection.request(uri).object)

    def _to_protocols(self, object):
        protocols = []
        for item in object["protocols"]:
            protocols.append(item['name'].lower())
        return protocols

    def _to_balancers(self, object):
        return [self._to_balancer(el) for el in object["loadBalancers"]]

    def _to_balancer(self, el):
        ip = None
        port = None

        if 'virtualIps' in el:
            ip = el["virtualIps"][0]["address"]

        if 'port' in el:
            port = el["port"]

        lb = LoadBalancer(id=el["id"],
                          name=el["name"],
                          state=self.LB_STATE_MAP.get(el["status"],
                                                      State.UNKNOWN),
                          ip=ip,
                          port=port,
                          driver=self.connection.driver)
        return lb

    def _to_members(self, object):
        return [self._to_member(el) for el in object["nodes"]]

    def _to_member(self, el):
        lbmember = Member(id=el["id"], ip=el["address"], port=el["port"])
        return lbmember
Ejemplo n.º 8
0
class BrightboxLBDriver(Driver):
    connectionCls = BrightboxConnection

    name = 'Brightbox'
    website = 'http://www.brightbox.co.uk/'

    LB_STATE_MAP = {
        'creating': State.PENDING,
        'active': State.RUNNING,
        'deleting': State.UNKNOWN,
        'deleted': State.UNKNOWN,
        'failing': State.UNKNOWN,
        'failed': State.UNKNOWN,
    }

    _VALUE_TO_ALGORITHM_MAP = {
        'round-robin': Algorithm.ROUND_ROBIN,
        'least-connections': Algorithm.LEAST_CONNECTIONS
    }

    _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)

    def list_protocols(self):
        return ['tcp', 'http']

    def list_balancers(self):
        data = self.connection.request('/%s/load_balancers' % API_VERSION) \
                   .object

        return list(map(self._to_balancer, data))

    def create_balancer(self, name, port, protocol, algorithm, members):
        response = self._post(
            '/%s/load_balancers' % API_VERSION, {
                'name': name,
                'nodes': list(map(self._member_to_node, members)),
                'policy': self._algorithm_to_value(algorithm),
                'listeners': [{
                    'in': port,
                    'out': port,
                    'protocol': protocol
                }],
                'healthcheck': {
                    'type': protocol,
                    'port': port
                }
            })

        return self._to_balancer(response.object)

    def destroy_balancer(self, balancer):
        response = self.connection.request('/%s/load_balancers/%s' %
                                           (API_VERSION, balancer.id),
                                           method='DELETE')

        return response.status == httplib.ACCEPTED

    def get_balancer(self, balancer_id):
        data = self.connection.request('/%s/load_balancers/%s' %
                                       (API_VERSION, balancer_id)).object
        return self._to_balancer(data)

    def balancer_attach_compute_node(self, balancer, node):
        return self.balancer_attach_member(balancer, node)

    def balancer_attach_member(self, balancer, member):
        path = '/%s/load_balancers/%s/add_nodes' % (API_VERSION, balancer.id)

        self._post(path, {'nodes': [self._member_to_node(member)]})

        return member

    def balancer_detach_member(self, balancer, member):
        path = '/%s/load_balancers/%s/remove_nodes' % (API_VERSION,
                                                       balancer.id)

        response = self._post(path, {'nodes': [self._member_to_node(member)]})

        return response.status == httplib.ACCEPTED

    def balancer_list_members(self, balancer):
        path = '/%s/load_balancers/%s' % (API_VERSION, balancer.id)

        data = self.connection.request(path).object

        def func(data):
            return self._node_to_member(data, balancer)

        return list(map(func, data['nodes']))

    def _post(self, path, data={}):
        headers = {'Content-Type': 'application/json'}

        return self.connection.request(path,
                                       data=data,
                                       headers=headers,
                                       method='POST')

    def _to_balancer(self, data):
        return LoadBalancer(id=data['id'],
                            name=data['name'],
                            state=self.LB_STATE_MAP.get(
                                data['status'], State.UNKNOWN),
                            ip=self._public_ip(data),
                            port=data['listeners'][0]['in'],
                            driver=self.connection.driver)

    def _member_to_node(self, member):
        return {'node': member.id}

    def _node_to_member(self, data, balancer):
        return Member(id=data['id'], ip=None, port=None, balancer=balancer)

    def _public_ip(self, data):
        if len(data['cloud_ips']) > 0:
            ip = data['cloud_ips'][0]['public_ip']
        else:
            ip = None

        return ip
Ejemplo n.º 9
0
class RackspaceLBDriver(Driver):
    connectionCls = RackspaceConnection
    api_name = 'rackspace_lb'
    name = 'Rackspace LB'

    LB_STATE_MAP = {
        'ACTIVE': State.RUNNING,
        'BUILD': State.PENDING,
        'ERROR': State.ERROR,
        'DELETED': State.DELETED,
        'PENDING_UPDATE': State.PENDING,
        'PENDING_DELETE': State.PENDING
    }

    LB_MEMBER_CONDITION_MAP = {
        'ENABLED': MemberCondition.ENABLED,
        'DISABLED': MemberCondition.DISABLED,
        'DRAINING': MemberCondition.DRAINING
    }

    _VALUE_TO_ALGORITHM_MAP = {
        'RANDOM': Algorithm.RANDOM,
        'ROUND_ROBIN': Algorithm.ROUND_ROBIN,
        'LEAST_CONNECTIONS': Algorithm.LEAST_CONNECTIONS,
        'WEIGHTED_ROUND_ROBIN': Algorithm.WEIGHTED_ROUND_ROBIN,
        'WEIGHTED_LEAST_CONNECTIONS': Algorithm.WEIGHTED_LEAST_CONNECTIONS
    }

    _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)

    def list_protocols(self):
        return self._to_protocols(
            self.connection.request('/loadbalancers/protocols').object)

    def list_balancers(self, ex_member_address=None):
        """
        @param ex_member_address: Optional IP address of the attachment member.
                                  If provided, only the load balancers which
                                  have this member attached will be returned.
        @type ex_member_address: C{str}
        """
        params = {}

        if ex_member_address:
            params['nodeaddress'] = ex_member_address

        return self._to_balancers(
            self.connection.request('/loadbalancers', params=params).object)

    def create_balancer(self,
                        name,
                        members,
                        protocol='http',
                        port=80,
                        algorithm=DEFAULT_ALGORITHM):
        balancer_attrs = self._kwargs_to_mutable_attrs(name=name,
                                                       protocol=protocol,
                                                       port=port,
                                                       algorithm=algorithm)

        balancer_attrs.update({
            "virtualIps": [{
                "type": "PUBLIC"
            }],
            "nodes": [{
                "address": member.ip,
                "port": member.port,
                "condition": "ENABLED"
            } for member in members],
        })
        balancer_object = {"loadBalancer": balancer_attrs}

        resp = self.connection.request('/loadbalancers',
                                       method='POST',
                                       data=json.dumps(balancer_object))
        return self._to_balancer(resp.object["loadBalancer"])

    def destroy_balancer(self, balancer):
        uri = '/loadbalancers/%s' % (balancer.id)
        resp = self.connection.request(uri, method='DELETE')

        return resp.status == httplib.ACCEPTED

    def get_balancer(self, balancer_id):
        uri = '/loadbalancers/%s' % (balancer_id)
        resp = self.connection.request(uri)

        return self._to_balancer(resp.object["loadBalancer"])

    def balancer_attach_member(self, balancer, member):
        ip = member.ip
        port = member.port

        member_object = {
            "nodes": [{
                "port": port,
                "address": ip,
                "condition": "ENABLED"
            }]
        }

        uri = '/loadbalancers/%s/nodes' % (balancer.id)
        resp = self.connection.request(uri,
                                       method='POST',
                                       data=json.dumps(member_object))
        return self._to_members(resp.object)[0]

    def balancer_detach_member(self, balancer, member):
        # Loadbalancer always needs to have at least 1 member.
        # Last member cannot be detached. You can only disable it or destroy
        # the balancer.
        uri = '/loadbalancers/%s/nodes/%s' % (balancer.id, member.id)
        resp = self.connection.request(uri, method='DELETE')

        return resp.status == httplib.ACCEPTED

    def balancer_list_members(self, balancer):
        uri = '/loadbalancers/%s/nodes' % (balancer.id)
        return self._to_members(self.connection.request(uri).object)

    def update_balancer(self, balancer, **kwargs):
        attrs = self._kwargs_to_mutable_attrs(**kwargs)
        resp = self.connection.async_request(action='/loadbalancers/%s' %
                                             balancer.id,
                                             method='PUT',
                                             data=json.dumps(attrs))
        return self._to_balancer(resp.object["loadBalancer"])

    def ex_update_balancer_no_poll(self, balancer, **kwargs):
        attrs = self._kwargs_to_mutable_attrs(**kwargs)
        resp = self.connection.request(action='/loadbalancers/%s' %
                                       balancer.id,
                                       method='PUT',
                                       data=json.dumps(attrs))
        return resp.status == httplib.ACCEPTED

    def ex_list_algorithm_names(self):
        """
        Lists algorithms supported by the API.  Returned as strings because
        this list may change in the future.
        """
        response = self.connection.request('/loadbalancers/algorithms')
        return [a["name"].upper() for a in response.object["algorithms"]]

    def ex_get_balancer_error_page(self, balancer):
        uri = '/loadbalancers/%s/errorpage' % (balancer.id)
        resp = self.connection.request(uri)

        return resp.object["errorpage"]["content"]

    def ex_balancer_access_list(self, balancer):
        uri = '/loadbalancers/%s/accesslist' % (balancer.id)
        resp = self.connection.request(uri)

        return [self._to_access_rule(el) for el in resp.object["accessList"]]

    def _to_protocols(self, object):
        protocols = []
        for item in object["protocols"]:
            protocols.append(item['name'].lower())
        return protocols

    def _to_balancers(self, object):
        return [self._to_balancer(el) for el in object["loadBalancers"]]

    def _to_balancer(self, el):
        ip = None
        port = None
        sourceAddresses = {}

        if 'virtualIps' in el:
            ip = el["virtualIps"][0]["address"]

        if 'port' in el:
            port = el["port"]

        if 'sourceAddresses' in el:
            sourceAddresses = el['sourceAddresses']

        extra = {
            "publicVips": self._ex_public_virtual_ips(el),
            "privateVips": self._ex_private_virtual_ips(el),
            "ipv6PublicSource": sourceAddresses.get("ipv6Public"),
            "ipv4PublicSource": sourceAddresses.get("ipv4Public"),
            "ipv4PrivateSource": sourceAddresses.get("ipv4Servicenet"),
        }

        if 'protocol' in el:
            extra['protocol'] = el['protocol']

        if 'algorithm' in el and el["algorithm"] in \
            self._VALUE_TO_ALGORITHM_MAP:
            extra["algorithm"] = self._value_to_algorithm(el["algorithm"])

        if 'healthMonitor' in el:
            health_monitor = self._to_health_monitor(el)
            if health_monitor:
                extra["healthMonitor"] = health_monitor

        if 'connectionThrottle' in el:
            extra["connectionThrottle"] = self._to_connection_throttle(el)

        if 'sessionPersistence' in el:
            persistence = el["sessionPersistence"]
            extra["sessionPersistenceType"] = \
                    persistence.get("persistenceType")

        if 'connectionLogging' in el:
            logging = el["connectionLogging"]
            extra["connectionLoggingEnabled"] = logging.get("enabled")

        if 'nodes' in el:
            extra['members'] = self._to_members(el)

        if 'created' in el:
            extra['created'] = self._iso_to_datetime(el['created']['time'])

        if 'updated' in el:
            extra['updated'] = self._iso_to_datetime(el['updated']['time'])

        return LoadBalancer(id=el["id"],
                            name=el["name"],
                            state=self.LB_STATE_MAP.get(
                                el["status"], State.UNKNOWN),
                            ip=ip,
                            port=port,
                            driver=self.connection.driver,
                            extra=extra)

    def _to_members(self, object):
        return [self._to_member(el) for el in object["nodes"]]

    def _to_member(self, el):
        extra = {}
        if 'weight' in el:
            extra['weight'] = el["weight"]

        if 'condition' in el and el['condition'] in \
           self.LB_MEMBER_CONDITION_MAP:
            extra['condition'] = \
                    self.LB_MEMBER_CONDITION_MAP.get(el["condition"])

        if 'status' in el:
            extra['status'] = el["status"]

        lbmember = Member(id=el["id"],
                          ip=el["address"],
                          port=el["port"],
                          extra=extra)
        return lbmember

    def _kwargs_to_mutable_attrs(self, **attrs):
        update_attrs = {}
        if "name" in attrs:
            update_attrs['name'] = attrs['name']

        if "algorithm" in attrs:
            algorithm_value = self._algorithm_to_value(attrs['algorithm'])
            update_attrs['algorithm'] = algorithm_value

        if "protocol" in attrs:
            update_attrs['protocol'] = attrs['protocol'].upper()

        if "port" in attrs:
            update_attrs['port'] = int(attrs['port'])

        return update_attrs

    def _ex_private_virtual_ips(self, el):
        if not 'virtualIps' in el:
            return None

        servicenet_vips = [
            ip for ip in el['virtualIps'] if ip['type'] == 'SERVICENET'
        ]
        return [vip["address"] for vip in servicenet_vips]

    def _ex_public_virtual_ips(self, el):
        if not 'virtualIps' in el:
            return None

        public_vips = [ip for ip in el['virtualIps'] if ip['type'] == 'PUBLIC']
        return [vip["address"] for vip in public_vips]

    def _to_health_monitor(self, el):
        health_monitor_data = el["healthMonitor"]

        type = health_monitor_data.get("type")
        delay = health_monitor_data.get("delay")
        timeout = health_monitor_data.get("timeout")
        attempts_before_deactivation = \
                health_monitor_data.get("attemptsBeforeDeactivation")

        if type == "CONNECT":
            return RackspaceHealthMonitor(
                type=type,
                delay=delay,
                timeout=timeout,
                attempts_before_deactivation=attempts_before_deactivation)

        if type == "HTTP" or type == "HTTPS":
            return RackspaceHTTPHealthMonitor(
                type=type,
                delay=delay,
                timeout=timeout,
                attempts_before_deactivation=attempts_before_deactivation,
                path=health_monitor_data.get("path"),
                status_regex=health_monitor_data.get("statusRegex"),
                body_regex=health_monitor_data.get("bodyRegex"))

        return None

    def _to_connection_throttle(self, el):
        connection_throttle_data = el["connectionThrottle"]

        min_connections = connection_throttle_data.get("minConnections")
        max_connections = connection_throttle_data.get("maxConnections")
        max_connection_rate = connection_throttle_data.get("maxConnectionRate")
        rate_interval = connection_throttle_data.get("rateInterval")

        return RackspaceConnectionThrottle(
            min_connections=min_connections,
            max_connections=max_connections,
            max_connection_rate=max_connection_rate,
            rate_interval_seconds=rate_interval)

    def _to_access_rule(self, el):
        return RackspaceAccessRule(id=el.get("id"),
                                   rule_type=self._to_access_rule_type(
                                       el.get("type")),
                                   address=el.get("address"))

    def _to_access_rule_type(self, type):
        if type == "ALLOW":
            return RackspaceAccessRuleType.ALLOW
        elif type == "DENY":
            return RackspaceAccessRuleType.DENY

    def _iso_to_datetime(self, isodate):
        date_formats = ('%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%dT%H:%M:%S%z')
        date = None

        for date_format in date_formats:
            try:
                date = datetime.strptime(isodate, date_format)
            except ValueError:
                pass

            if date:
                break

        return date
Ejemplo n.º 10
0
class GoGridLBDriver(BaseGoGridDriver, Driver):
    connectionCls = GoGridLBConnection
    api_name = "gogrid_lb"
    name = "GoGrid LB"
    website = "http://www.gogrid.com/"

    LB_STATE_MAP = {"On": State.RUNNING, "Unknown": State.UNKNOWN}
    _VALUE_TO_ALGORITHM_MAP = {
        "round robin": Algorithm.ROUND_ROBIN,
        "least connect": Algorithm.LEAST_CONNECTIONS,
    }
    _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)

    def __init__(self, *args, **kwargs):
        """
        @inherits: :class:`Driver.__init__`
        """
        super(GoGridLBDriver, self).__init__(*args, **kwargs)

    def list_protocols(self):
        # GoGrid only supports http
        return ["http"]

    def list_balancers(self):
        return self._to_balancers(
            self.connection.request("/api/grid/loadbalancer/list").object)

    def ex_create_balancer_nowait(self,
                                  name,
                                  members,
                                  protocol="http",
                                  port=80,
                                  algorithm=DEFAULT_ALGORITHM):
        """
        @inherits: :class:`Driver.create_balancer`
        """
        algorithm = self._algorithm_to_value(algorithm)

        params = {
            "name": name,
            "loadbalancer.type": algorithm,
            "virtualip.ip": self._get_first_ip(),
            "virtualip.port": port,
        }
        params.update(self._members_to_params(members))

        resp = self.connection.request("/api/grid/loadbalancer/add",
                                       method="GET",
                                       params=params)
        return self._to_balancers(resp.object)[0]

    def create_balancer(self,
                        name,
                        members,
                        protocol="http",
                        port=80,
                        algorithm=DEFAULT_ALGORITHM):
        balancer = self.ex_create_balancer_nowait(name, members, protocol,
                                                  port, algorithm)

        timeout = 60 * 20
        waittime = 0
        interval = 2 * 15

        if balancer.id is not None:
            return balancer
        else:
            while waittime < timeout:
                balancers = self.list_balancers()

                for i in balancers:
                    if i.name == balancer.name and i.id is not None:
                        return i

                waittime += interval
                time.sleep(interval)

        raise Exception("Failed to get id")

    def destroy_balancer(self, balancer):
        try:
            resp = self.connection.request(
                "/api/grid/loadbalancer/delete",
                method="POST",
                params={"id": balancer.id},
            )
        except Exception as e:
            if "Update request for LoadBalancer" in str(e):
                raise LibcloudLBImmutableError(
                    "Cannot delete immutable object", GoGridLBDriver)
            else:
                raise

        return resp.status == 200

    def get_balancer(self, **kwargs):
        params = {}

        try:
            params["name"] = kwargs["ex_balancer_name"]
        except KeyError:
            balancer_id = kwargs["balancer_id"]
            params["id"] = balancer_id

        resp = self.connection.request("/api/grid/loadbalancer/get",
                                       params=params)

        return self._to_balancers(resp.object)[0]

    def balancer_attach_member(self, balancer, member):
        members = self.balancer_list_members(balancer)
        members.append(member)

        params = {"id": balancer.id}

        params.update(self._members_to_params(members))

        resp = self._update_balancer(params)
        return [
            m for m in self._to_members(resp.object["list"][0]["realiplist"],
                                        balancer) if m.ip == member.ip
        ][0]

    def balancer_detach_member(self, balancer, member):
        members = self.balancer_list_members(balancer)

        remaining_members = [n for n in members if n.id != member.id]

        params = {"id": balancer.id}
        params.update(self._members_to_params(remaining_members))

        resp = self._update_balancer(params)

        return resp.status == 200

    def balancer_list_members(self, balancer):
        resp = self.connection.request("/api/grid/loadbalancer/get",
                                       params={"id": balancer.id})
        return self._to_members(resp.object["list"][0]["realiplist"], balancer)

    def _update_balancer(self, params):
        try:
            return self.connection.request("/api/grid/loadbalancer/edit",
                                           method="POST",
                                           params=params)
        except Exception as e:
            if "Update already pending" in str(e):
                raise LibcloudLBImmutableError("Balancer is immutable",
                                               GoGridLBDriver)

            raise LibcloudError(value="Exception: %s" % str(e), driver=self)

    def _members_to_params(self, members):
        """
        Helper method to convert list of :class:`Member` objects
        to GET params.

        """

        params = {}

        i = 0
        for member in members:
            params["realiplist.%s.ip" % i] = member.ip
            params["realiplist.%s.port" % i] = member.port
            i += 1

        return params

    def _to_balancers(self, object):
        return [self._to_balancer(el) for el in object["list"]]

    def _to_balancer(self, el):
        lb = LoadBalancer(
            id=el.get("id"),
            name=el["name"],
            state=self.LB_STATE_MAP.get(el["state"]["name"], State.UNKNOWN),
            ip=el["virtualip"]["ip"]["ip"],
            port=el["virtualip"]["port"],
            driver=self.connection.driver,
        )
        return lb

    def _to_members(self, object, balancer=None):
        return [self._to_member(el, balancer) for el in object]

    def _to_member(self, el, balancer=None):
        member = Member(id=el["ip"]["id"],
                        ip=el["ip"]["ip"],
                        port=el["port"],
                        balancer=balancer)
        return member
Ejemplo n.º 11
0
class SoftlayerLBDriver(Driver):
    name = "Softlayer Load Balancing"
    website = "http://www.softlayer.com/"
    connectionCls = SoftLayerConnection

    _VALUE_TO_ALGORITHM_MAP = {
        "ROUND_ROBIN": Algorithm.ROUND_ROBIN,
        "LEAST_CONNECTIONS": Algorithm.LEAST_CONNECTIONS,
        "SHORTEST_RESPONSE": Algorithm.SHORTEST_RESPONSE,
        "PERSISTENT_IP": Algorithm.PERSISTENT_IP,
    }

    _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)

    def list_balancers(self):
        mask = {
            "adcLoadBalancers": {
                "ipAddress": "",
                "loadBalancerHardware": {
                    "datacenter": ""
                },
                "virtualServers": {
                    "serviceGroups": {
                        "routingMethod": "",
                        "routingType": "",
                        "services": {
                            "ipAddress": ""
                        },
                    }
                },
            }
        }

        res = self.connection.request("SoftLayer_Account",
                                      "getAdcLoadBalancers",
                                      object_mask=mask).object

        return [self._to_balancer(lb) for lb in res]

    def get_balancer(self, balancer_id):
        balancers = self.list_balancers()
        balancer = find(balancers, lambda b: b.id == balancer_id)
        if not balancer:
            raise LibcloudError(value="No balancer found for id: %s" %
                                balancer_id,
                                driver=self)
        return balancer

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

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

    def balancer_list_members(self, balancer):
        lb = self._get_balancer_model(balancer.id)
        members = []
        vs = self._locate_service_group(lb, balancer.port)
        if vs:
            if vs["serviceGroups"]:
                srvgrp = vs["serviceGroups"][0]
                members = [
                    self._to_member(srv, balancer)
                    for srv in srvgrp["services"]
                ]

        return members

    def balancer_attach_member(self, balancer, member):
        lb = self._get_balancer_model(balancer.id)
        vs = self._locate_service_group(lb, balancer.port)
        if not vs:
            raise LibcloudError(
                value="No service_group found for balancer "
                "port: %s" % balancer.port,
                driver=self,
            )

        if vs["serviceGroups"]:
            services = vs["serviceGroups"][0]["services"]
            services.append(self._to_service_template(member.ip, member.port))

        self.connection.request(lb_service, "editObject", lb, id=balancer.id)

        return [m for m in balancer.list_members() if m.ip == member.ip][0]

    def balancer_detach_member(self, balancer, member):
        svc_lbsrv = ("SoftLayer_Network_Application_Delivery_Controller_"
                     "LoadBalancer_Service")

        self.connection.request(svc_lbsrv, "deleteObject", id=member.id)
        return True

    def destroy_balancer(self, balancer):
        res_billing = self.connection.request(lb_service,
                                              "getBillingItem",
                                              id=balancer.id).object

        self.connection.request("SoftLayer_Billing_Item",
                                "cancelService",
                                id=res_billing["id"])
        return True

    def ex_list_balancer_packages(self):
        """
        Retrieves the available local load balancer packages.

        :rtype: ``list`` of :class:`LBPackage`
        """
        mask = {"prices": ""}
        res = self.connection.request("SoftLayer_Product_Package",
                                      "getItems",
                                      id=0,
                                      object_mask=mask).object

        res_lb_pkgs = [
            r for r in res if r["description"].find("Load Balancer") != -1
        ]
        res_lb_pkgs = [
            r for r in res_lb_pkgs if not r["description"].startswith("Global")
        ]

        return [self._to_lb_package(r) for r in res_lb_pkgs]

    def ex_place_balancer_order(self, package, location):
        """
        Places an order for a local loadbalancer in the specified
        location.

        :param package: The package to create the loadbalancer from.
        :type package: :class:`LBPackage`

        :param string location: The location (datacenter) to create the
                                loadbalancer.
        :type location: :class:`NodeLocation`

        :return: ``True`` if ex_place_balancer_order was successful.
        :rtype: ``bool``
        """
        data = {
            "complexType": "SoftLayer_Container_Product_Order_Network_"
            "LoadBalancer",
            "quantity": 1,
            "packageId": 0,
            "location": self._get_location(location.id),
            "prices": [{
                "id": package.price_id
            }],
        }

        self.connection.request("SoftLayer_Product_Order", "placeOrder", data)
        return True

    def ex_configure_load_balancer(
        self,
        balancer,
        port=80,
        protocol="http",
        algorithm=DEFAULT_ALGORITHM,
        ex_allocation=100,
    ):
        """
        Configure the loadbalancer by adding it with a front-end port (aka
        a service group in the Softlayer loadbalancer model).

        Softlayer loadbalancer may be defined with multiple service
        groups (front-end ports) each defined with a unique port number.

        :param balancer: The loadbalancer.
        :type  balancer: :class:`LoadBalancer`

        :param port: Port of the service group, defaults to 80.
        :type  port: ``int``

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

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

        :param ex_allocation: The percentage of the total connection
                              allocations to allocate for this group.
        :type  ex_allocation: ``int``

        :return: ``True`` if ex_add_service_group was successful.
        :rtype: ``bool``
        """
        _types = self._get_routing_types()
        _methods = self._get_routing_methods()

        rt = find(_types, lambda t: t["keyname"] == protocol.upper())
        if not rt:
            raise LibcloudError(value="Invalid protocol %s" % protocol,
                                driver=self)

        value = self._algorithm_to_value(algorithm)
        meth = find(_methods, lambda m: m["keyname"] == value)
        if not meth:
            raise LibcloudError(value="Invalid algorithm %s" % algorithm,
                                driver=self)

        service_group_template = {
            "port":
            port,
            "allocation":
            ex_allocation,
            "serviceGroups": [{
                "routingTypeId": rt["id"],
                "routingMethodId": meth["id"]
            }],
        }

        lb = self._get_balancer_model(balancer.id)
        if len(lb["virtualServers"]) > 0:
            port = lb["virtualServers"][0]["port"]
            raise LibcloudError(
                value="Loadbalancer already configured with "
                "a service group (front-end port)" % port,
                driver=self,
            )

        lb["virtualServers"].append(service_group_template)
        self.connection.request(lb_service, "editObject", lb, id=balancer.id)
        return True

    def _get_balancer_model(self, balancer_id):
        """
        Retrieve Softlayer loadbalancer model.
        """
        lb_mask = {
            "virtualServers": {
                "serviceGroups": {
                    "services": {
                        "ipAddress": "",
                        "groupReferences": ""
                    }
                }
            }
        }

        lb_res = self.connection.request(lb_service,
                                         "getObject",
                                         object_mask=lb_mask,
                                         id=balancer_id).object
        return lb_res

    def _locate_service_group(self, lb, port):
        """
        Locate service group with given port.

        Return virtualServers (vs) entry whose port matches the
        supplied parameter port. For a negative port, just return
        the first vs entry.
        None is returned if no match found.

        :param lb: Softlayer loadbalancer model.
        :type lb: ``dict``

        :param port: loadbalancer front-end port.
        :type port: ``int``

        :return: Matched entry in the virtualServers array of the supplied
        model.
        :rtype: ``dict``
        """
        vs = None
        if port < 0:
            vs = lb["virtualServers"][0] if lb["virtualServers"] else None
        else:
            vs = find(lb["virtualServers"], lambda v: v["port"] == port)

        return vs

    def _get_routing_types(self):
        svc_rtype = ("SoftLayer_Network_Application_Delivery_Controller_"
                     "LoadBalancer_Routing_Type")

        return self.connection.request(svc_rtype, "getAllObjects").object

    def _get_routing_methods(self):
        svc_rmeth = ("SoftLayer_Network_Application_Delivery_Controller_"
                     "LoadBalancer_Routing_Method")

        return self.connection.request(svc_rmeth, "getAllObjects").object

    def _get_location(self, location_id):
        res = self.connection.request("SoftLayer_Location_Datacenter",
                                      "getDatacenters").object

        dcenter = find(res, lambda d: d["name"] == location_id)
        if not dcenter:
            raise LibcloudError(value="Invalid value %s" % location_id,
                                driver=self)
        return dcenter["id"]

    def _get_ipaddress(self, ip):
        svc_ipaddress = "SoftLayer_Network_Subnet_IpAddress"

        return self.connection.request(svc_ipaddress, "getByIpAddress",
                                       ip).object

    def _to_lb_package(self, pkg):
        try:
            price_id = pkg["prices"][0]["id"]
        except Exception:
            price_id = -1

        capacity = int(pkg.get("capacity", 0))
        return LBPackage(
            id=pkg["id"],
            name=pkg["keyName"],
            description=pkg["description"],
            price_id=price_id,
            capacity=capacity,
        )

    def _to_service_template(self, ip, port):
        """
        Builds single member entry in Softlayer loadbalancer model
        """
        template = {
            "enabled": 1,  # enable the service
            "port": port,  # back-end port
            "ipAddressId": self._get_ipaddress(ip)["id"],
            "healthChecks": [{
                "healthCheckTypeId": 21
            }],  # default health check
            "groupReferences": [{
                "weight": 1
            }],
        }

        return template

    def _to_balancer(self, lb):
        ipaddress = lb["ipAddress"]["ipAddress"]

        extra = {}
        extra["connection_limit"] = lb["connectionLimit"]
        extra["ssl_active"] = lb["sslActiveFlag"]
        extra["ssl_enabled"] = lb["sslEnabledFlag"]
        extra["ha"] = lb["highAvailabilityFlag"]
        extra["datacenter"] = lb["loadBalancerHardware"][0]["datacenter"][
            "name"]

        # In Softlayer, there could be multiple group of members (aka service
        # groups), so retrieve the first one
        vs = self._locate_service_group(lb, -1)
        if vs:
            port = vs["port"]
            if vs["serviceGroups"]:
                srvgrp = vs["serviceGroups"][0]
                routing_method = srvgrp["routingMethod"]["keyname"]
                routing_type = srvgrp["routingType"]["keyname"]
                try:
                    extra["algorithm"] = self._value_to_algorithm(
                        routing_method)
                except Exception:
                    pass
                extra["protocol"] = routing_type.lower()

        if not vs:
            port = -1

        balancer = LoadBalancer(
            id=lb["id"],
            name="",
            state=State.UNKNOWN,
            ip=ipaddress,
            port=port,
            driver=self.connection.driver,
            extra=extra,
        )

        return balancer

    def _to_member(self, srv, balancer=None):
        svc_id = srv["id"]
        ip = srv["ipAddress"]["ipAddress"]
        port = srv["port"]

        extra = {}
        extra["status"] = srv["status"]
        extra["enabled"] = srv["enabled"]
        return Member(id=svc_id,
                      ip=ip,
                      port=port,
                      balancer=balancer,
                      extra=extra)
Ejemplo n.º 12
0
class CloudStackLBDriver(CloudStackDriverMixIn, Driver):
    """Driver for CloudStack load balancers."""

    api_name = "cloudstack_lb"
    name = "CloudStack"
    website = "http://cloudstack.org/"
    type = Provider.CLOUDSTACK

    _VALUE_TO_ALGORITHM_MAP = {
        "roundrobin": Algorithm.ROUND_ROBIN,
        "leastconn": Algorithm.LEAST_CONNECTIONS,
    }
    _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)

    LB_STATE_MAP = {
        "Active": State.RUNNING,
    }

    def __init__(
        self,
        key,
        secret=None,
        secure=True,
        host=None,
        path=None,
        port=None,
        *args,
        **kwargs,
    ):
        """
        @inherits: :class:`Driver.__init__`
        """
        host = host if host else self.host
        path = path if path else self.path

        if path is not None:
            self.path = path

        if host is not None:
            self.host = host

        if (self.type == Provider.CLOUDSTACK) and (not host or not path):
            raise Exception(
                "When instantiating CloudStack driver directly "
                + "you also need to provide host and path argument"
            )

        super(CloudStackLBDriver, self).__init__(
            key=key, secret=secret, secure=secure, host=host, port=port
        )

    def list_protocols(self):
        """
        We don't actually have any protocol awareness beyond TCP.

        :rtype: ``list`` of ``str``
        """
        return ["tcp"]

    def list_balancers(self):
        balancers = self._sync_request(command="listLoadBalancerRules", method="GET")
        balancers = balancers.get("loadbalancerrule", [])
        return [self._to_balancer(balancer) for balancer in balancers]

    def get_balancer(self, balancer_id):
        balancer = self._sync_request(
            command="listLoadBalancerRules", params={"id": balancer_id}, method="GET"
        )
        balancer = balancer.get("loadbalancerrule", [])
        if not balancer:
            raise Exception("no such load balancer: " + str(balancer_id))
        return self._to_balancer(balancer[0])

    def create_balancer(
        self,
        name,
        members,
        protocol="http",
        port=80,
        algorithm=DEFAULT_ALGORITHM,
        location=None,
        private_port=None,
        network_id=None,
        vpc_id=None,
    ):
        """
        @inherits: :class:`Driver.create_balancer`

        :param location: Location
        :type  location: :class:`NodeLocation`

        :param private_port: Private port
        :type  private_port: ``int``

        :param network_id: The guest network this rule will be created for.
        :type  network_id: ``str``
        """

        args = {}
        ip_args = {}

        if location is None:
            locations = self._sync_request(command="listZones", method="GET")
            location = locations["zone"][0]["id"]
        else:
            location = location.id
        if private_port is None:
            private_port = port

        if network_id is not None:
            args["networkid"] = network_id
            ip_args["networkid"] = network_id

        if vpc_id is not None:
            ip_args["vpcid"] = vpc_id

        ip_args.update({"zoneid": location, "networkid": network_id, "vpc_id": vpc_id})

        result = self._async_request(
            command="associateIpAddress", params=ip_args, method="GET"
        )
        public_ip = result["ipaddress"]

        args.update(
            {
                "algorithm": self._ALGORITHM_TO_VALUE_MAP[algorithm],
                "name": name,
                "privateport": private_port,
                "publicport": port,
                "publicipid": public_ip["id"],
            }
        )

        result = self._sync_request(
            command="createLoadBalancerRule", params=args, method="GET"
        )

        listbalancers = self._sync_request(
            command="listLoadBalancerRules", params=args, method="GET"
        )

        listbalancers = [
            rule
            for rule in listbalancers["loadbalancerrule"]
            if rule["id"] == result["id"]
        ]
        if len(listbalancers) != 1:
            return None

        balancer = self._to_balancer(listbalancers[0])

        for member in members:
            balancer.attach_member(member)

        return balancer

    def destroy_balancer(self, balancer):
        self._async_request(
            command="deleteLoadBalancerRule", params={"id": balancer.id}, method="GET"
        )
        self._async_request(
            command="disassociateIpAddress",
            params={"id": balancer.ex_public_ip_id},
            method="GET",
        )

    def balancer_attach_member(self, balancer, member):
        member.port = balancer.ex_private_port
        self._async_request(
            command="assignToLoadBalancerRule",
            params={"id": balancer.id, "virtualmachineids": member.id},
            method="GET",
        )
        return True

    def balancer_detach_member(self, balancer, member):
        self._async_request(
            command="removeFromLoadBalancerRule",
            params={"id": balancer.id, "virtualmachineids": member.id},
            method="GET",
        )
        return True

    def balancer_list_members(self, balancer):
        members = self._sync_request(
            command="listLoadBalancerRuleInstances",
            params={"id": balancer.id},
            method="GET",
        )
        members = members["loadbalancerruleinstance"]
        return [self._to_member(m, balancer.ex_private_port, balancer) for m in members]

    def _to_balancer(self, obj):
        balancer = LoadBalancer(
            id=obj["id"],
            name=obj["name"],
            state=self.LB_STATE_MAP.get(obj["state"], State.UNKNOWN),
            ip=obj["publicip"],
            port=obj["publicport"],
            driver=self.connection.driver,
        )
        balancer.ex_private_port = obj["privateport"]
        balancer.ex_public_ip_id = obj["publicipid"]
        return balancer

    def _to_member(self, obj, port, balancer):
        return Member(
            id=obj["id"], ip=obj["nic"][0]["ipaddress"], port=port, balancer=balancer
        )
Ejemplo n.º 13
0
class BrightboxLBDriver(Driver):
    connectionCls = BrightboxConnection

    name = "Brightbox"
    website = "http://www.brightbox.co.uk/"

    LB_STATE_MAP = {
        "creating": State.PENDING,
        "active": State.RUNNING,
        "deleting": State.UNKNOWN,
        "deleted": State.UNKNOWN,
        "failing": State.UNKNOWN,
        "failed": State.UNKNOWN,
    }

    _VALUE_TO_ALGORITHM_MAP = {
        "round-robin": Algorithm.ROUND_ROBIN,
        "least-connections": Algorithm.LEAST_CONNECTIONS,
    }

    _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)

    def list_protocols(self):
        return ["tcp", "http"]

    def list_balancers(self):
        data = self.connection.request("/%s/load_balancers" % API_VERSION).object

        return list(map(self._to_balancer, data))

    def create_balancer(self, name, port, protocol, algorithm, members):
        response = self._post(
            "/%s/load_balancers" % API_VERSION,
            {
                "name": name,
                "nodes": list(map(self._member_to_node, members)),
                "policy": self._algorithm_to_value(algorithm),
                "listeners": [{"in": port, "out": port, "protocol": protocol}],
                "healthcheck": {"type": protocol, "port": port},
            },
        )

        return self._to_balancer(response.object)

    def destroy_balancer(self, balancer):
        response = self.connection.request(
            "/%s/load_balancers/%s" % (API_VERSION, balancer.id), method="DELETE"
        )

        return response.status == httplib.ACCEPTED

    def get_balancer(self, balancer_id):
        data = self.connection.request(
            "/%s/load_balancers/%s" % (API_VERSION, balancer_id)
        ).object
        return self._to_balancer(data)

    def balancer_attach_compute_node(self, balancer, node):
        return self.balancer_attach_member(balancer, node)

    def balancer_attach_member(self, balancer, member):
        path = "/%s/load_balancers/%s/add_nodes" % (API_VERSION, balancer.id)

        self._post(path, {"nodes": [self._member_to_node(member)]})

        return member

    def balancer_detach_member(self, balancer, member):
        path = "/%s/load_balancers/%s/remove_nodes" % (API_VERSION, balancer.id)

        response = self._post(path, {"nodes": [self._member_to_node(member)]})

        return response.status == httplib.ACCEPTED

    def balancer_list_members(self, balancer):
        path = "/%s/load_balancers/%s" % (API_VERSION, balancer.id)

        data = self.connection.request(path).object

        def func(data):
            return self._node_to_member(data, balancer)

        return list(map(func, data["nodes"]))

    def _post(self, path, data={}):
        headers = {"Content-Type": "application/json"}

        return self.connection.request(path, data=data, headers=headers, method="POST")

    def _to_balancer(self, data):
        return LoadBalancer(
            id=data["id"],
            name=data["name"],
            state=self.LB_STATE_MAP.get(data["status"], State.UNKNOWN),
            ip=self._public_ip(data),
            port=data["listeners"][0]["in"],
            driver=self.connection.driver,
        )

    def _member_to_node(self, member):
        return {"node": member.id}

    def _node_to_member(self, data, balancer):
        return Member(id=data["id"], ip=None, port=None, balancer=balancer)

    def _public_ip(self, data):
        if len(data["cloud_ips"]) > 0:
            ip = data["cloud_ips"][0]["public_ip"]
        else:
            ip = None

        return ip