Repository: libcloud Updated Branches: refs/heads/trunk ccc53c520 -> 5a9bf7c96
Loadbalancer driver for Softlayer Closes #500 Signed-off-by: Tomaz Muraus <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/d232f792 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/d232f792 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/d232f792 Branch: refs/heads/trunk Commit: d232f7929a133d188f5787f9c8e8d06acdd8bedf Parents: ccc53c5 Author: Avi Weit <[email protected]> Authored: Mon Apr 6 06:10:46 2015 +0000 Committer: Tomaz Muraus <[email protected]> Committed: Thu Apr 16 14:00:13 2015 -0400 ---------------------------------------------------------------------- CHANGES.rst | 8 + .../add_list_remove_balancer_members.py | 27 ++ .../loadbalancer/softlayer/destroy_balancer.py | 11 + .../softlayer/ex_place_balancer_order.py | 25 ++ .../loadbalancer/softlayer/list_balancers.py | 13 + libcloud/loadbalancer/base.py | 7 +- libcloud/loadbalancer/drivers/softlayer.py | 445 +++++++++++++++++++ libcloud/loadbalancer/providers.py | 2 + libcloud/loadbalancer/types.py | 1 + libcloud/utils/misc.py | 6 + 10 files changed, 543 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/d232f792/CHANGES.rst ---------------------------------------------------------------------- diff --git a/CHANGES.rst b/CHANGES.rst index ba5da15..8e8ef4e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -156,6 +156,14 @@ Storage (GITHUB-492, LIBCLOUD-635) [Tom Fifield] +Loadbalancer +~~~~~~~~~~~~ + +- Add a new driver for Softlayer load-balancing service + (https://www.softlayer.com/load-balancing). + (GITHUB-500, LIBCLOUD-688) + [Avi Weit] + DNS ~~~ http://git-wip-us.apache.org/repos/asf/libcloud/blob/d232f792/docs/examples/loadbalancer/softlayer/add_list_remove_balancer_members.py ---------------------------------------------------------------------- diff --git a/docs/examples/loadbalancer/softlayer/add_list_remove_balancer_members.py b/docs/examples/loadbalancer/softlayer/add_list_remove_balancer_members.py new file mode 100644 index 0000000..ad5bd39 --- /dev/null +++ b/docs/examples/loadbalancer/softlayer/add_list_remove_balancer_members.py @@ -0,0 +1,27 @@ +from libcloud.loadbalancer.base import DEFAULT_ALGORITHM, Member +from libcloud.loadbalancer.types import Provider +from libcloud.loadbalancer.providers import get_driver + +USER_NAME = 'your user name' +SECRET_KEY = 'your secret key' + +cls = get_driver(Provider.SOFTLAYER) +driver = cls(key=USER_NAME, secret=SECRET_KEY) + +balancer = driver.list_balancers()[0] + +if balancer.port < 0: + # no front-end port defined, configure it with such one + driver.ex_configure_load_balancer(balancer, port=80, protocol='http', + algorithm=DEFAULT_ALGORITHM) + +member1 = balancer.attach_member(Member(None, '192.168.88.1', 8000)) +member2 = balancer.attach_member(Member(None, '192.168.88.2', 8080)) + +print(balancer.list_members()) + +balancer.detach_member(member1) +print(balancer.list_members()) + +balancer.detach_member(member2) +print(balancer.list_members()) http://git-wip-us.apache.org/repos/asf/libcloud/blob/d232f792/docs/examples/loadbalancer/softlayer/destroy_balancer.py ---------------------------------------------------------------------- diff --git a/docs/examples/loadbalancer/softlayer/destroy_balancer.py b/docs/examples/loadbalancer/softlayer/destroy_balancer.py new file mode 100644 index 0000000..4ccc0d8 --- /dev/null +++ b/docs/examples/loadbalancer/softlayer/destroy_balancer.py @@ -0,0 +1,11 @@ +from libcloud.loadbalancer.types import Provider +from libcloud.loadbalancer.providers import get_driver + +USER_NAME = 'your user name' +SECRET_KEY = 'your secret key' + +cls = get_driver(Provider.SOFTLAYER) +driver = cls(key=USER_NAME, secret=SECRET_KEY) + +balancer = driver.list_balancers()[0] +balancer.destroy() http://git-wip-us.apache.org/repos/asf/libcloud/blob/d232f792/docs/examples/loadbalancer/softlayer/ex_place_balancer_order.py ---------------------------------------------------------------------- diff --git a/docs/examples/loadbalancer/softlayer/ex_place_balancer_order.py b/docs/examples/loadbalancer/softlayer/ex_place_balancer_order.py new file mode 100644 index 0000000..2810ce5 --- /dev/null +++ b/docs/examples/loadbalancer/softlayer/ex_place_balancer_order.py @@ -0,0 +1,25 @@ +from libcloud.compute.base import NodeLocation + +from libcloud.loadbalancer.types import Provider +from libcloud.loadbalancer.providers import get_driver + +USER_NAME = 'your user name' +SECRET_KEY = 'your secret key' + +cls = get_driver(Provider.SOFTLAYER) +driver = cls(key=USER_NAME, secret=SECRET_KEY) + +# order loadbalancer with a capacity of 50 connections +CAPACITY = 50 +# create the balancer in Dallas 5 datacenter +DATACENTER = 'dal05' + +# select package to create balancer from +packages = driver.ex_list_balancer_packages() +lb_package = [p for p in packages if p.capacity == CAPACITY][0] + +driver.ex_place_balancer_order(lb_package, + NodeLocation(DATACENTER, None, None, None)) + +print 'Successfully submitted oder request, from package' % \ + lb_package http://git-wip-us.apache.org/repos/asf/libcloud/blob/d232f792/docs/examples/loadbalancer/softlayer/list_balancers.py ---------------------------------------------------------------------- diff --git a/docs/examples/loadbalancer/softlayer/list_balancers.py b/docs/examples/loadbalancer/softlayer/list_balancers.py new file mode 100644 index 0000000..93872f2 --- /dev/null +++ b/docs/examples/loadbalancer/softlayer/list_balancers.py @@ -0,0 +1,13 @@ +from libcloud.loadbalancer.types import Provider +from libcloud.loadbalancer.providers import get_driver + +USER_NAME = 'your user name' +SECRET_KEY = 'your secret key' + +cls = get_driver(Provider.SOFTLAYER) +driver = cls(key=USER_NAME, secret=SECRET_KEY) + +balancers = driver.list_balancers() + +for balancer in balancers: + print '%s %s' % (balancer, balancer.extra) http://git-wip-us.apache.org/repos/asf/libcloud/blob/d232f792/libcloud/loadbalancer/base.py ---------------------------------------------------------------------- diff --git a/libcloud/loadbalancer/base.py b/libcloud/loadbalancer/base.py index 602ea22..3c65e5e 100644 --- a/libcloud/loadbalancer/base.py +++ b/libcloud/loadbalancer/base.py @@ -113,8 +113,9 @@ class LoadBalancer(object): return self.driver.destroy_balancer(balancer=self) def __repr__(self): - return ('<LoadBalancer: id=%s, name=%s, state=%s>' % (self.id, - self.name, self.state)) + return ('<LoadBalancer: id=%s, name=%s, state=%s, ip=%s, ' + 'port=%s>' % (self.id, self.name, self.state, self.ip, + self.port)) class Algorithm(object): @@ -127,6 +128,8 @@ class Algorithm(object): LEAST_CONNECTIONS = 2 WEIGHTED_ROUND_ROBIN = 3 WEIGHTED_LEAST_CONNECTIONS = 4 + SHORTEST_RESPONSE = 5 + PERSISTENT_IP = 6 DEFAULT_ALGORITHM = Algorithm.ROUND_ROBIN http://git-wip-us.apache.org/repos/asf/libcloud/blob/d232f792/libcloud/loadbalancer/drivers/softlayer.py ---------------------------------------------------------------------- diff --git a/libcloud/loadbalancer/drivers/softlayer.py b/libcloud/loadbalancer/drivers/softlayer.py new file mode 100644 index 0000000..99181b7 --- /dev/null +++ b/libcloud/loadbalancer/drivers/softlayer.py @@ -0,0 +1,445 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance withv +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from libcloud.common.types import LibcloudError + +__all__ = [ + 'SoftlayerLBDriver' +] + +from libcloud.utils.misc import find, reverse_dict +from libcloud.loadbalancer.types import State +from libcloud.loadbalancer.base import Algorithm, Driver, LoadBalancer,\ + DEFAULT_ALGORITHM, Member +from libcloud.common.softlayer import SoftLayerConnection + +lb_service = 'SoftLayer_Network_Application_Delivery_Controller_LoadBalancer_'\ + 'VirtualIpAddress' + + +class LBPackage(object): + + """ + Defines a single Softlayer package to be used when placing orders ( + e.g. via ex_place_balancer_order method). + + :param id: Package id. + :type id: ``int`` + + :param name: Package name. + :type name: ``str`` + + :param description: Package short description. + :type description: ``str`` + + :param price_id: Id of the price for this package. + :type price_id: ``int`` + + :param capacity: Provides a numerical representation of the capacity given + in the description of this package. + :type capacity: ``int`` + + """ + + def __init__(self, id, name, description, price_id, capacity): + self.id = id + self.name = name + self.description = description + self.price_id = price_id + self.capacity = capacity + + def __repr__(self): + return ( + '<LBPackage: id=%s, name=%s, description=%s, price_id=%s, ' + 'capacity=%s>' % (self.id, self.name, self.description, + self.price_id, self.capacity)) + + +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: + 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: + 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) http://git-wip-us.apache.org/repos/asf/libcloud/blob/d232f792/libcloud/loadbalancer/providers.py ---------------------------------------------------------------------- diff --git a/libcloud/loadbalancer/providers.py b/libcloud/loadbalancer/providers.py index f11e450..5a65402 100644 --- a/libcloud/loadbalancer/providers.py +++ b/libcloud/loadbalancer/providers.py @@ -38,6 +38,8 @@ DRIVERS = { ('libcloud.loadbalancer.drivers.cloudstack', 'CloudStackLBDriver'), Provider.GCE: ('libcloud.loadbalancer.drivers.gce', 'GCELBDriver'), + Provider.SOFTLAYER: + ('libcloud.loadbalancer.drivers.softlayer', 'SoftlayerLBDriver'), # Deprecated Provider.RACKSPACE_US: http://git-wip-us.apache.org/repos/asf/libcloud/blob/d232f792/libcloud/loadbalancer/types.py ---------------------------------------------------------------------- diff --git a/libcloud/loadbalancer/types.py b/libcloud/loadbalancer/types.py index 6be53e5..8ae82e9 100644 --- a/libcloud/loadbalancer/types.py +++ b/libcloud/loadbalancer/types.py @@ -39,6 +39,7 @@ class Provider(object): ELB = 'elb' CLOUDSTACK = 'cloudstack' GCE = 'gce' + SOFTLAYER = 'softlayer' # Deprecated RACKSPACE_US = 'rackspace_us' http://git-wip-us.apache.org/repos/asf/libcloud/blob/d232f792/libcloud/utils/misc.py ---------------------------------------------------------------------- diff --git a/libcloud/utils/misc.py b/libcloud/utils/misc.py index 3e2eeac..49c1a0c 100644 --- a/libcloud/utils/misc.py +++ b/libcloud/utils/misc.py @@ -19,6 +19,7 @@ import binascii __all__ = [ + 'find', 'get_driver', 'set_driver', 'merge_valid_keys', @@ -33,6 +34,11 @@ __all__ = [ ] +def find(l, predicate): + results = [x for x in l if predicate(x)] + return results[0] if len(results) > 0 else None + + def get_driver(drivers, provider): """ Get a driver.
