Author: tomaz
Date: Sun Jan 6 18:05:23 2013
New Revision: 1429563
URL: http://svn.apache.org/viewvc?rev=1429563&view=rev
Log:
Add a new driver for AWS Elastic Load Balancing service.
Contributed by John Carr, part of LIBCLOUD-169.
Added:
libcloud/trunk/libcloud/loadbalancer/drivers/elb.py
libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/
libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/create_load_balancer.xml
libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/deregister_instances_from_load_balancer.xml
libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/describe_load_balancers.xml
libcloud/trunk/libcloud/test/loadbalancer/test_elb.py
Modified:
libcloud/trunk/CHANGES
libcloud/trunk/libcloud/loadbalancer/providers.py
libcloud/trunk/libcloud/loadbalancer/types.py
libcloud/trunk/libcloud/test/secrets.py-dist
Modified: libcloud/trunk/CHANGES
URL:
http://svn.apache.org/viewvc/libcloud/trunk/CHANGES?rev=1429563&r1=1429562&r2=1429563&view=diff
==============================================================================
--- libcloud/trunk/CHANGES (original)
+++ libcloud/trunk/CHANGES Sun Jan 6 18:05:23 2013
@@ -136,6 +136,11 @@ Changes with Apache Libcloud in developm
- Finish Amazon Route53 driver. (LIBCLOUD-132)
[John Carr]
+ *) Load-Balancer
+
+ - Add new driver for AWS Elastic Load Balancing service. (LIBCLOUD-169)
+ [John Carr]
+
Changes with Apache Libcloud 0.11.4:
*) General
Added: libcloud/trunk/libcloud/loadbalancer/drivers/elb.py
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/loadbalancer/drivers/elb.py?rev=1429563&view=auto
==============================================================================
--- libcloud/trunk/libcloud/loadbalancer/drivers/elb.py (added)
+++ libcloud/trunk/libcloud/loadbalancer/drivers/elb.py Sun Jan 6 18:05:23 2013
@@ -0,0 +1,220 @@
+# 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 with
+# 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.
+
+__all__ = [
+ 'ElasticLBDriver'
+]
+
+import base64
+import hmac
+import time
+
+from hashlib import sha256
+
+from libcloud.utils.py3 import httplib, urlquote, b
+from libcloud.utils.xml import findtext, findall
+from libcloud.loadbalancer.types import State
+from libcloud.loadbalancer.base import Driver, LoadBalancer, Member
+from libcloud.common.types import InvalidCredsError
+from libcloud.common.aws import AWSBaseResponse
+from libcloud.common.base import ConnectionUserAndKey
+
+
+VERSION = '2012-06-01'
+HOST = 'elasticloadbalancing.%s.amazonaws.com'
+ROOT = '/%s/' % (VERSION)
+NS = 'http://elasticloadbalancing.amazonaws.com/doc/%s/' % (VERSION, )
+
+
+class ELBResponse(AWSBaseResponse):
+ """
+ Amazon ELB response class.
+ """
+ def success(self):
+ return self.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED]
+
+ def parse_error(self):
+ status = int(self.status)
+
+ if status == httplib.FORBIDDEN:
+ if not self.body:
+ raise InvalidCredsError(str(self.status) + ': ' + self.error)
+ else:
+ raise InvalidCredsError(self.body)
+
+
+class ELBConnection(ConnectionUserAndKey):
+ host = HOST
+ responseCls = ELBResponse
+
+ def add_default_params(self, params):
+ params['SignatureVersion'] = '2'
+ params['SignatureMethod'] = 'HmacSHA256'
+ params['AWSAccessKeyId'] = self.user_id
+ params['Version'] = VERSION
+ params['Timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ',
+ time.gmtime())
+ params['Signature'] = self._get_aws_auth_param(params, self.key,
+ self.action)
+ return params
+
+ def _get_aws_auth_param(self, params, secret_key, path='/'):
+ """
+ Creates the signature required for AWS, per
+ http://bit.ly/aR7GaQ [docs.amazonwebservices.com]:
+
+ StringToSign = HTTPVerb + "\n" +
+ ValueOfHostHeaderInLowercase + "\n" +
+ HTTPRequestURI + "\n" +
+ CanonicalizedQueryString <from the preceding step>
+ """
+ keys = list(params.keys())
+ keys.sort()
+ pairs = []
+ for key in keys:
+ pairs.append(urlquote(key, safe='') + '=' +
+ urlquote(params[key], safe='-_~'))
+
+ qs = '&'.join(pairs)
+
+ hostname = self.host
+ if (self.secure and self.port != 443) or \
+ (not self.secure and self.port != 80):
+ hostname += ":" + str(self.port)
+
+ string_to_sign = '\n'.join(('GET', hostname, path, qs))
+
+ b64_hmac = base64.b64encode(
+ hmac.new(b(secret_key), b(string_to_sign),
+ digestmod=sha256).digest()
+ )
+ return b64_hmac.decode('utf-8')
+
+
+class ElasticLBDriver(Driver):
+ name = 'ELB'
+ website = 'http://aws.amazon.com/elasticloadbalancing/'
+ connectionCls = ELBConnection
+
+ def __init__(self, access_id, secret, region):
+ super(ElasticLBDriver, self).__init__(access_id, secret)
+ self.region = region
+ self.connection.host = HOST % (region)
+
+ def list_protocols(self):
+ return ['tcp', 'ssl', 'http', 'https']
+
+ def list_balancers(self):
+ params = {'Action': 'DescribeLoadBalancers'}
+ data = self.connection.request(ROOT, params=params).object
+ return self._to_balancers(data)
+
+ def create_balancer(self, name, port, protocol, algorithm, members,
+ ex_members_availability_zones=None):
+ if ex_members_availability_zones is None:
+ ex_members_availability_zones = ['a']
+
+ params = {
+ 'Action': 'CreateLoadBalancer',
+ 'LoadBalancerName': name,
+ 'Listeners.member.1.InstancePort': str(port),
+ 'Listeners.member.1.InstanceProtocol': protocol.upper(),
+ 'Listeners.member.1.LoadBalancerPort': str(port),
+ 'Listeners.member.1.Protocol': protocol.upper(),
+ }
+
+ for i, z in enumerate(ex_members_availability_zones, 1):
+ zone = '-'.join((self.region, z))
+ params['AvailabilityZones.member.%d' % i] = zone
+
+ data = self.connection.request(ROOT, params=params).object
+
+ balancer = LoadBalancer(
+ id=name,
+ name=name,
+ state=State.PENDING,
+ ip=findtext(element=data, xpath='DNSName', namespace=NS),
+ port=port,
+ driver=self.connection.driver
+ )
+ balancer._members = []
+ return balancer
+
+ def destroy_balancer(self, balancer):
+ params = {
+ 'Action': 'DeleteLoadBalancer',
+ 'LoadBalancerName': balancer.id
+ }
+ self.connection.request(ROOT, params=params)
+ return True
+
+ def get_balancer(self, balancer_id):
+ params = {
+ 'Action': 'DescribeLoadBalancers',
+ 'LoadBalancerNames.member.1': balancer_id
+ }
+ data = self.connection.request(ROOT, params=params).object
+ return self._to_balancers(data)[0]
+
+ def balancer_attach_compute_node(self, balancer, node):
+ params = {
+ 'Action': 'RegisterInstancesWithLoadBalancer',
+ 'LoadBalancerName': balancer.id,
+ 'Instances.member.1.InstanceId': node.id
+ }
+ self.connection.request(ROOT, params=params)
+ balancer._members.append(Member(node.id, None, None, balancer=self))
+
+ def balancer_detach_member(self, balancer, member):
+ params = {
+ 'Action': 'DeregisterInstancesFromLoadBalancer',
+ 'LoadBalancerName': balancer.id,
+ 'Instances.member.1.InstanceId': member.id
+ }
+ self.connection.request(ROOT, params=params)
+ balancer._members = [m for m in balancer._members if m.id != member.id]
+ return True
+
+ def balancer_list_members(self, balancer):
+ return balancer._members
+
+ def _to_balancers(self, data):
+ xpath = 'DescribeLoadBalancersResult/LoadBalancerDescriptions/member'
+ return [self._to_balancer(el)
+ for el in findall(element=data, xpath=xpath, namespace=NS)]
+
+ def _to_balancer(self, el):
+ name = findtext(element=el, xpath='LoadBalancerName', namespace=NS)
+ dns_name = findtext(el, xpath='DNSName', namespace=NS)
+ port = findtext(el, xpath='LoadBalancerPort', namespace=NS)
+
+ balancer = LoadBalancer(
+ id=name,
+ name=name,
+ state=State.UNKNOWN,
+ ip=dns_name,
+ port=port,
+ driver=self.connection.driver
+ )
+
+ xpath = 'Instances/member/InstanceId'
+ members = findall(element=el, xpath=xpath, namespace=NS)
+ balancer._members = []
+
+ for m in members:
+ balancer._members.append(Member(m.text, None, None,
+ balancer=balancer))
+
+ return balancer
Modified: libcloud/trunk/libcloud/loadbalancer/providers.py
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/loadbalancer/providers.py?rev=1429563&r1=1429562&r2=1429563&view=diff
==============================================================================
--- libcloud/trunk/libcloud/loadbalancer/providers.py (original)
+++ libcloud/trunk/libcloud/loadbalancer/providers.py Sun Jan 6 18:05:23 2013
@@ -33,7 +33,9 @@ DRIVERS = {
Provider.NINEFOLD:
('libcloud.loadbalancer.drivers.ninefold', 'NinefoldLBDriver'),
Provider.BRIGHTBOX:
- ('libcloud.loadbalancer.drivers.brightbox', 'BrightboxLBDriver')
+ ('libcloud.loadbalancer.drivers.brightbox', 'BrightboxLBDriver'),
+ Provider.ELB:
+ ('libcloud.loadbalancer.drivers.elb', 'ElasticLBDriver')
}
Modified: libcloud/trunk/libcloud/loadbalancer/types.py
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/loadbalancer/types.py?rev=1429563&r1=1429562&r2=1429563&view=diff
==============================================================================
--- libcloud/trunk/libcloud/loadbalancer/types.py (original)
+++ libcloud/trunk/libcloud/loadbalancer/types.py Sun Jan 6 18:05:23 2013
@@ -37,6 +37,7 @@ class Provider(object):
NINEFOLD = 'ninefold'
RACKSPACE_UK = 'rackspace_uk'
BRIGHTBOX = 'brightbox'
+ ELB = 'elb'
class State(object):
Added:
libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/create_load_balancer.xml
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/create_load_balancer.xml?rev=1429563&view=auto
==============================================================================
---
libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/create_load_balancer.xml
(added)
+++
libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/create_load_balancer.xml
Sun Jan 6 18:05:23 2013
@@ -0,0 +1,3 @@
+<CreateLoadBalancerResult
xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
+ <DNSName>tests.example.com</DNSName>
+</CreateLoadBalancerResult>
Added:
libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/deregister_instances_from_load_balancer.xml
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/deregister_instances_from_load_balancer.xml?rev=1429563&view=auto
==============================================================================
---
libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/deregister_instances_from_load_balancer.xml
(added)
+++
libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/deregister_instances_from_load_balancer.xml
Sun Jan 6 18:05:23 2013
@@ -0,0 +1,4 @@
+<DeregisterInstancesFromLoadBalancer
xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
+ <Instances>
+ </Instances>
+</DeregisterInstancesFromLoadBalancer>
\ No newline at end of file
Added:
libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/describe_load_balancers.xml
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/describe_load_balancers.xml?rev=1429563&view=auto
==============================================================================
---
libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/describe_load_balancers.xml
(added)
+++
libcloud/trunk/libcloud/test/loadbalancer/fixtures/elb/describe_load_balancers.xml
Sun Jan 6 18:05:23 2013
@@ -0,0 +1,61 @@
+<DescribeLoadBalancersResponse
xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
+ <DescribeLoadBalancersResult>
+ <LoadBalancerDescriptions>
+ <member>
+ <SecurityGroups>
+ </SecurityGroups>
+ <LoadBalancerName>tests</LoadBalancerName>
+ <CreatedTime>2013-01-01T00:00:00.19000Z</CreatedTime>
+ <HealthCheck>
+ <Interval>30</Interval>
+ <Target>TCP:22</Target>
+ <HealthyThreshold>10</HealthyThreshold>
+ <Timeout>5</Timeout>
+ <UnhealthyThreshold>2</UnhealthyThreshold>
+ </HealthCheck>
+ <VPCId>vpc-56e10e3d</VPCId>
+ <ListenerDescriptions>
+ <member>
+ <PolicyNames>
+ <member>AWSConsolePolicy-1</member>
+ </PolicyNames>
+ <Listener>
+ <Protocol>HTTP</Protocol>
+ <LoadBalancerPort>80</LoadBalancerPort>
+ <InstanceProtocol>HTTP</InstanceProtocol>
+ <InstancePort>80</InstancePort>
+ </Listener>
+ </member>
+ </ListenerDescriptions>
+ <Instances>
+ <member>
+ <InstanceId>i-64bd081c</InstanceId>
+ </member>
+ </Instances>
+ <Policies>
+ <AppCookieStickinessPolicies/>
+ <OtherPolicies/>
+ <LBCookieStickinessPolicies>
+ <member>
+ <PolicyName>AWSConsolePolicy-1</PolicyName>
+ <CookieExpirationPeriod>30</CookieExpirationPeriod>
+ </member>
+ </LBCookieStickinessPolicies>
+ </Policies>
+ <AvailabilityZones>
+ <member>us-east-1e</member>
+ </AvailabilityZones>
+
<CanonicalHostedZoneName>tests.us-east-1.elb.amazonaws.com</CanonicalHostedZoneName>
+ <CanonicalHostedZoneNameID>Z3ZONEID</CanonicalHostedZoneNameID>
+ <Scheme>internet-facing</Scheme>
+ <DNSName>tests.us-east-1.elb.amazonaws.com</DNSName>
+ <BackendServerDescriptions/>
+ <Subnets>
+ </Subnets>
+ </member>
+ </LoadBalancerDescriptions>
+ </DescribeLoadBalancersResult>
+ <ResponseMetadata>
+ <RequestId>f9880f01-7852-629d-a6c3-3ae2-666a409287e6dc0c</RequestId>
+ </ResponseMetadata>
+</DescribeLoadBalancersResponse>
Added: libcloud/trunk/libcloud/test/loadbalancer/test_elb.py
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/loadbalancer/test_elb.py?rev=1429563&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/loadbalancer/test_elb.py (added)
+++ libcloud/trunk/libcloud/test/loadbalancer/test_elb.py Sun Jan 6 18:05:23
2013
@@ -0,0 +1,112 @@
+# 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 with
+# 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.
+
+import sys
+import unittest
+
+from libcloud.utils.py3 import httplib
+from libcloud.loadbalancer.base import Member, Algorithm
+from libcloud.loadbalancer.drivers.elb import ElasticLBDriver
+from libcloud.loadbalancer.types import State
+
+from libcloud.test import MockHttpTestCase
+from libcloud.test.secrets import LB_ELB_PARAMS
+from libcloud.test.file_fixtures import LoadBalancerFileFixtures
+
+
+class ElasticLBTests(unittest.TestCase):
+ def setUp(self):
+ ElasticLBMockHttp.test = self
+ ElasticLBDriver.connectionCls.conn_classes = (None,
+ ElasticLBMockHttp)
+ ElasticLBMockHttp.type = None
+ ElasticLBMockHttp.use_param = 'Action'
+
+ self.driver = ElasticLBDriver(*LB_ELB_PARAMS)
+
+ def test_list_protocols(self):
+ protocols = self.driver.list_protocols()
+
+ self.assertEqual(len(protocols), 4)
+ self.assertTrue('tcp' in protocols)
+ self.assertTrue('http' in protocols)
+
+ def test_list_balancers(self):
+ balancers = self.driver.list_balancers()
+
+ self.assertEquals(len(balancers), 1)
+ self.assertEquals(balancers[0].id, 'tests')
+ self.assertEquals(balancers[0].name, 'tests')
+
+ def test_get_balancer(self):
+ balancer = self.driver.get_balancer(balancer_id='tests')
+
+ self.assertEquals(balancer.id, 'tests')
+ self.assertEquals(balancer.name, 'tests')
+ self.assertEquals(balancer.state, State.UNKNOWN)
+
+ def test_destroy_balancer(self):
+ balancer = self.driver.get_balancer(balancer_id='tests')
+
+ self.assertTrue(self.driver.destroy_balancer(balancer))
+
+ def test_create_balancer(self):
+ members = [Member('srv-lv426', None, None)]
+
+ balancer = self.driver.create_balancer(name='lb2', port=80,
+ protocol='http', algorithm=Algorithm.ROUND_ROBIN,
+ members=members)
+
+ self.assertEquals(balancer.name, 'lb2')
+ self.assertEquals(balancer.port, 80)
+ self.assertEquals(balancer.state, State.PENDING)
+
+ def test_balancer_list_members(self):
+ balancer = self.driver.get_balancer(balancer_id='tests')
+ members = balancer.list_members()
+
+ self.assertEquals(len(members), 1)
+ self.assertEquals(members[0].balancer, balancer)
+ self.assertEquals('i-64bd081c', members[0].id)
+
+ def test_balancer_detach_member(self):
+ balancer = self.driver.get_balancer(balancer_id='lba-1235f')
+ member = Member('i-64bd081c', None, None)
+
+ self.assertTrue(balancer.detach_member(member))
+
+
+class ElasticLBMockHttp(MockHttpTestCase):
+ fixtures = LoadBalancerFileFixtures('elb')
+
+ def _2012_06_01_DescribeLoadBalancers(self, method, url, body, headers):
+ body = self.fixtures.load('describe_load_balancers.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _2012_06_01_CreateLoadBalancer(self, method, url, body, headers):
+ body = self.fixtures.load('create_load_balancer.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _2012_06_01_DeregisterInstancesFromLoadBalancer(self, method, url,
body, headers):
+ body =
self.fixtures.load('deregister_instances_from_load_balancer.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _2012_06_01_DeleteLoadBalancer(self, method, url, body, headers):
+ body = ''
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+
+if __name__ == "__main__":
+ sys.exit(unittest.main())
Modified: libcloud/trunk/libcloud/test/secrets.py-dist
URL:
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/secrets.py-dist?rev=1429563&r1=1429562&r2=1429563&view=diff
==============================================================================
--- libcloud/trunk/libcloud/test/secrets.py-dist (original)
+++ libcloud/trunk/libcloud/test/secrets.py-dist Sun Jan 6 18:05:23 2013
@@ -46,6 +46,7 @@ STORAGE_GOOGLE_STORAGE_PARAMS = ('key',
# Loadbalancer
LB_BRIGHTBOX_PARAMS = ('user', 'key')
+LB_ELB_PARAMS = ('access_id', 'secret', 'region')
# DNS
DNS_PARAMS_LINODE = ('user', 'key')