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',
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
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)
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
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)
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 )
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
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
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
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
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)
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 )
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