Author: tomaz Date: Thu Dec 22 20:52:15 2011 New Revision: 1222444 URL: http://svn.apache.org/viewvc?rev=1222444&view=rev Log: 1. Add 'request_kwargs' kwarg to the 'get_poll_request_kwargs' method. This argument contains kwargs which were previously used to initiate the poll request.
2. Add update_balancer method to the base LoadBalancer class. 3. Add update_balancer and ex_update_balancer_no_poll method to the Rackspace LoadBalancer driver. This patch has been contributed by Mark Everett <[email protected]> and is part of GH-47. Added: libcloud/trunk/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_3xxx.json Modified: libcloud/trunk/CHANGES libcloud/trunk/libcloud/common/base.py libcloud/trunk/libcloud/common/cloudstack.py libcloud/trunk/libcloud/dns/drivers/rackspace.py libcloud/trunk/libcloud/loadbalancer/base.py libcloud/trunk/libcloud/loadbalancer/drivers/rackspace.py libcloud/trunk/test/loadbalancer/test_rackspace.py Modified: libcloud/trunk/CHANGES URL: http://svn.apache.org/viewvc/libcloud/trunk/CHANGES?rev=1222444&r1=1222443&r2=1222444&view=diff ============================================================================== --- libcloud/trunk/CHANGES (original) +++ libcloud/trunk/CHANGES Thu Dec 22 20:52:15 2011 @@ -2,6 +2,13 @@ Changes with Apache Libcloud in development: + *) General: + + - Add 'request_kwargs' argument to the get_poll_request_kwargs method. + This argument contains kwargs which were previously used to initiate the + poll request. + [Mark Everett] + *) Compute: - Added support for retrieving OpenNebula v3.2 instance types, OpenNebula @@ -63,6 +70,12 @@ Changes with Apache Libcloud in developm (Rackspace driver). [Mark Everett] + - Add update_balancer method to the base LoadBalancer class. + [Mark Everett] + + - Add update_balancer method to the Rackspace driver. + [Mark Everett] + Changes with Apache Libcloud 0.7.1: *) General: Modified: libcloud/trunk/libcloud/common/base.py URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/common/base.py?rev=1222444&r1=1222443&r2=1222444&view=diff ============================================================================== --- libcloud/trunk/libcloud/common/base.py (original) +++ libcloud/trunk/libcloud/common/base.py Thu Dec 22 20:52:15 2011 @@ -618,14 +618,16 @@ class PollingConnection(Connection): context=context) response = request(**kwargs) kwargs = self.get_poll_request_kwargs(response=response, - context=context) + context=context, + request_kwargs=kwargs) end = time.time() + self.timeout completed = False while time.time() < end and not completed: response = request(**kwargs) completed = self.has_completed(response=response) - time.sleep(self.poll_interval) + if not completed: + time.sleep(self.poll_interval) if not completed: raise LibcloudError('Job did not complete in %s seconds' % @@ -643,7 +645,7 @@ class PollingConnection(Connection): 'headers': headers, 'method': method} return kwargs - def get_poll_request_kwargs(self, response, context): + def get_poll_request_kwargs(self, response, context, request_kwargs): """ Return keyword arguments which are passed to the request() method when polling for the job status. @@ -651,6 +653,10 @@ class PollingConnection(Connection): @param response: Response object returned by poll request. @type response: C{HTTPResponse} + @param request_kwargs: Kwargs previously used to initiate the + poll request. + @type response: C{dict} + @return C{dict} Keyword arguments """ raise NotImplementedError('get_poll_request_kwargs not implemented') Modified: libcloud/trunk/libcloud/common/cloudstack.py URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/common/cloudstack.py?rev=1222444&r1=1222443&r2=1222444&view=diff ============================================================================== --- libcloud/trunk/libcloud/common/cloudstack.py (original) +++ libcloud/trunk/libcloud/common/cloudstack.py Thu Dec 22 20:52:15 2011 @@ -73,7 +73,7 @@ class CloudStackConnection(ConnectionUse method='GET', context=None): return context - def get_poll_request_kwargs(self, response, context): + def get_poll_request_kwargs(self, response, context, request_kwargs): job_id = response['jobid'] kwargs = {'command': 'queryAsyncJobResult', 'jobid': job_id} return kwargs Modified: libcloud/trunk/libcloud/dns/drivers/rackspace.py URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/dns/drivers/rackspace.py?rev=1222444&r1=1222443&r2=1222444&view=diff ============================================================================== --- libcloud/trunk/libcloud/dns/drivers/rackspace.py (original) +++ libcloud/trunk/libcloud/dns/drivers/rackspace.py Thu Dec 22 20:52:15 2011 @@ -87,7 +87,7 @@ class RackspaceDNSConnection(OpenStack_1 poll_interval = 2.5 timeout = 30 - def get_poll_request_kwargs(self, response, context): + def get_poll_request_kwargs(self, response, context, request_kwargs): job_id = response.object['jobId'] kwargs = {'action': '/status/%s' % (job_id), 'params': {'showDetails': True}} Modified: libcloud/trunk/libcloud/loadbalancer/base.py URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/loadbalancer/base.py?rev=1222444&r1=1222443&r2=1222444&view=diff ============================================================================== --- libcloud/trunk/libcloud/loadbalancer/base.py (original) +++ libcloud/trunk/libcloud/loadbalancer/base.py Thu Dec 22 20:52:15 2011 @@ -163,6 +163,25 @@ class Driver(BaseDriver): raise NotImplementedError( 'get_balancer not implemented for this driver') + def update_balancer(self, balancer, **kwargs): + """ + Sets the name, algorithm, protocol, or port on a load balancer. + + @keyword name: New load balancer name + @type metadata: C{str} + + @keyword algorithm: New load balancer algorithm + @type metadata: C{libcloud.loadbalancer.base.Algorithm} + + @keyword protocol: New load balancer protocol + @type metadata: C{str} + + @keyword port: New load balancer port + @type metadata: C{int} + """ + raise NotImplementedError( + 'update_balancer not implemented for this driver') + def balancer_attach_compute_node(self, balancer, node): """ Attach a compute node as a member to the load balancer. Modified: libcloud/trunk/libcloud/loadbalancer/drivers/rackspace.py URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/loadbalancer/drivers/rackspace.py?rev=1222444&r1=1222443&r2=1222444&view=diff ============================================================================== --- libcloud/trunk/libcloud/loadbalancer/drivers/rackspace.py (original) +++ libcloud/trunk/libcloud/loadbalancer/drivers/rackspace.py Thu Dec 22 20:52:15 2011 @@ -21,10 +21,12 @@ try: except ImportError: import json +from libcloud.utils.py3 import httplib from libcloud.utils.misc import reverse_dict -from libcloud.common.base import JsonResponse from libcloud.loadbalancer.base import LoadBalancer, Member, Driver, Algorithm from libcloud.loadbalancer.base import DEFAULT_ALGORITHM +from libcloud.common.types import LibcloudError +from libcloud.common.base import JsonResponse, PollingConnection from libcloud.loadbalancer.types import State, MemberCondition from libcloud.common.openstack import OpenStackBaseConnection from libcloud.common.rackspace import ( @@ -105,7 +107,7 @@ class RackspaceConnectionThrottle(object @param max_connections: Maximum number of of connections per IP address. (Must be between 0 and 100000, 0 allows an - unlimited number of connections. + unlimited number of connections.) @type max_connections: C{int} @param max_connection_rate: Maximum number of connections allowed @@ -155,10 +157,12 @@ class RackspaceAccessRule(object): self.address = address -class RackspaceConnection(OpenStackBaseConnection): +class RackspaceConnection(OpenStackBaseConnection, PollingConnection): responseCls = RackspaceResponse auth_url = AUTH_URL_US _url_key = "lb_url" + poll_interval = 2 + timeout = 80 def __init__(self, user_id, key, secure=True, **kwargs): super(RackspaceConnection, self).__init__(user_id, key, secure, @@ -181,6 +185,17 @@ class RackspaceConnection(OpenStackBaseC return super(RackspaceConnection, self).request(action=action, params=params, data=data, method=method, headers=headers) + def get_poll_request_kwargs(self, response, context, request_kwargs): + return {'action': request_kwargs['action'], + 'method': 'GET'} + + def has_completed(self, response): + state = response.object['loadBalancer']['status'] + if state == 'ERROR': + raise LibcloudError("Load balancer entered an ERROR state.", + driver=self.driver) + + return state == 'ACTIVE' class RackspaceUKConnection(RackspaceConnection): auth_url = AUTH_URL_UK @@ -238,19 +253,19 @@ class RackspaceLBDriver(Driver): 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], - } - } + 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', @@ -261,7 +276,7 @@ class RackspaceLBDriver(Driver): uri = '/loadbalancers/%s' % (balancer.id) resp = self.connection.request(uri, method='DELETE') - return resp.status == 202 + return resp.status == httplib.ACCEPTED def get_balancer(self, balancer_id): uri = '/loadbalancers/%s' % (balancer_id) @@ -291,13 +306,29 @@ class RackspaceLBDriver(Driver): uri = '/loadbalancers/%s/nodes/%s' % (balancer.id, member.id) resp = self.connection.request(uri, method='DELETE') - return resp.status == 202 + 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 @@ -407,6 +438,23 @@ class RackspaceLBDriver(Driver): 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 Added: libcloud/trunk/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_3xxx.json URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_3xxx.json?rev=1222444&view=auto ============================================================================== --- libcloud/trunk/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_3xxx.json (added) +++ libcloud/trunk/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_3xxx.json Thu Dec 22 20:52:15 2011 @@ -0,0 +1,46 @@ +{ + "loadBalancer": { + "algorithm": "UUUUUUUUUU", + "cluster": { + "name": "ztm-n05.lbaas.ord1.rackspace.net" + }, + "connectionLogging": { + "enabled": false + }, + "created": { + "time": "2011-04-07T16:27:50Z" + }, + "id": 9999999, + "name": "test2", + "nodes": [ + { + "address": "10.1.0.11", + "condition": "ENABLED", + "id": 30944, + "port": 80, + "status": "ONLINE" + }, + { + "address": "10.1.0.10", + "condition": "ENABLED", + "id": 30945, + "port": 80, + "status": "ONLINE" + } + ], + "port": 88888, + "protocol": "XXXXX", + "status": "ACTIVE", + "updated": { + "time": "2011-04-07T16:28:12Z" + }, + "virtualIps": [ + { + "address": "1.1.1.1", + "id": 1151, + "ipVersion": "IPV4", + "type": "PUBLIC" + } + ] + } +} Modified: libcloud/trunk/test/loadbalancer/test_rackspace.py URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/test_rackspace.py?rev=1222444&r1=1222443&r2=1222444&view=diff ============================================================================== --- libcloud/trunk/test/loadbalancer/test_rackspace.py (original) +++ libcloud/trunk/test/loadbalancer/test_rackspace.py Thu Dec 22 20:52:15 2011 @@ -23,11 +23,12 @@ except ImportError: from libcloud.utils.py3 import httplib -from libcloud.loadbalancer.base import Member, Algorithm +from libcloud.loadbalancer.base import LoadBalancer, Member, Algorithm from libcloud.loadbalancer.types import MemberCondition from libcloud.loadbalancer.drivers.rackspace import RackspaceLBDriver from libcloud.loadbalancer.drivers.rackspace import RackspaceUKLBDriver from libcloud.loadbalancer.drivers.rackspace import RackspaceAccessRuleType +from libcloud.common.types import LibcloudError from test import MockHttpTestCase from test.file_fixtures import LoadBalancerFileFixtures, OpenStackFixtures @@ -40,6 +41,7 @@ class RackspaceLBTests(unittest.TestCase RackspaceLBMockHttp) RackspaceLBMockHttp.type = None self.driver = RackspaceLBDriver('user', 'key') + self.driver.connection.poll_interval = 0.0 def test_list_protocols(self): protocols = self.driver.list_protocols() @@ -293,6 +295,94 @@ class RackspaceLBTests(unittest.TestCase ret = balancer.detach_member(member) self.assertTrue(ret) + def test_update_balancer_protocol(self): + balancer = LoadBalancer(id='3130', name='LB_update', + state='PENDING_UPDATE', ip='10.34.4.3', + port=80, driver=self.driver) + updated_balancer = self.driver.update_balancer(balancer, protocol='HTTPS') + self.assertEqual('HTTPS', updated_balancer.extra['protocol']) + + def test_update_balancer_port(self): + balancer = LoadBalancer(id='3131', name='LB_update', + state='PENDING_UPDATE', ip='10.34.4.3', + port=80, driver=self.driver) + updated_balancer = self.driver.update_balancer(balancer, port=1337) + self.assertEqual(1337, updated_balancer.port) + + def test_update_balancer_name(self): + balancer = LoadBalancer(id='3132', name='LB_update', + state='PENDING_UPDATE', ip='10.34.4.3', + port=80, driver=self.driver) + updated_balancer = self.driver.update_balancer(balancer, name='new_lb_name') + self.assertEqual('new_lb_name', updated_balancer.name) + + def test_update_balancer_algorithm(self): + balancer = LoadBalancer(id='3133', name='LB_update', + state='PENDING_UPDATE', ip='10.34.4.3', + port=80, driver=self.driver) + updated_balancer = self.driver.update_balancer(balancer, + algorithm=Algorithm.ROUND_ROBIN) + self.assertEqual(Algorithm.ROUND_ROBIN, updated_balancer.extra['algorithm']) + + def test_update_balancer_bad_algorithm_exception(self): + balancer = LoadBalancer(id='3134', name='LB_update', + state='PENDING_UPDATE', ip='10.34.4.3', + port=80, driver=self.driver) + try: + self.driver.update_balancer(balancer, + algorithm='HAVE_MERCY_ON_OUR_SERVERS') + except LibcloudError: + pass + else: + self.fail('Should have thrown an exception with bad algorithm value') + + def test_ex_update_balancer_no_poll_protocol(self): + balancer = LoadBalancer(id='3130', name='LB_update', + state='PENDING_UPDATE', ip='10.34.4.3', + port=80, driver=self.driver) + action_succeeded = self.driver.ex_update_balancer_no_poll( + balancer, + protocol='HTTPS') + self.assertTrue(action_succeeded) + + def test_ex_update_balancer_no_poll_port(self): + balancer = LoadBalancer(id='3131', name='LB_update', + state='PENDING_UPDATE', ip='10.34.4.3', + port=80, driver=self.driver) + action_succeeded = self.driver.ex_update_balancer_no_poll( + balancer, + port=1337) + self.assertTrue(action_succeeded) + + def test_ex_update_balancer_no_poll_name(self): + balancer = LoadBalancer(id='3132', name='LB_update', + state='PENDING_UPDATE', ip='10.34.4.3', + port=80, driver=self.driver) + + action_succeeded = self.driver.ex_update_balancer_no_poll( + balancer, + name='new_lb_name') + self.assertTrue(action_succeeded) + + def test_ex_update_balancer_no_poll_algorithm(self): + balancer = LoadBalancer(id='3133', name='LB_update', + state='PENDING_UPDATE', ip='10.34.4.3', + port=80, driver=self.driver) + action_succeeded = self.driver.ex_update_balancer_no_poll(balancer, + algorithm=Algorithm.ROUND_ROBIN) + self.assertTrue(action_succeeded) + + def test_ex_update_balancer_no_poll_bad_algorithm_exception(self): + balancer = LoadBalancer(id='3134', name='LB_update', + state='PENDING_UPDATE', ip='10.34.4.3', + port=80, driver=self.driver) + try: + self.driver.update_balancer(balancer, + algorithm='HAVE_MERCY_ON_OUR_SERVERS') + except LibcloudError: + pass + else: + self.fail('Should have thrown exception with bad algorithm value') class RackspaceUKLBTests(RackspaceLBTests): @@ -302,7 +392,6 @@ class RackspaceUKLBTests(RackspaceLBTest RackspaceLBMockHttp.type = None self.driver = RackspaceUKLBDriver('user', 'key') - class RackspaceLBMockHttp(MockHttpTestCase): fixtures = LoadBalancerFileFixtures('rackspace') auth_fixtures = OpenStackFixtures() @@ -451,6 +540,60 @@ class RackspaceLBMockHttp(MockHttpTestCa raise NotImplementedError + def _v1_0_slug_loadbalancers_3130(self, method, url, body, headers): + """ update_balancer(b, protocol='HTTPS'), then get_balancer('3130') """ + if method == "PUT": + self.assertEqual(json.loads(body), {'protocol': 'HTTPS'}) + return (httplib.ACCEPTED, "", {}, httplib.responses[httplib.ACCEPTED]) + elif method == "GET": + response_body = json.loads(self.fixtures.load("v1_slug_loadbalancers_3xxx.json")) + response_body['loadBalancer']['id'] = 3130 + response_body['loadBalancer']['protocol'] = 'HTTPS' + return (httplib.OK, json.dumps(response_body), {}, httplib.responses[httplib.OK]) + raise NotImplementedError + + def _v1_0_slug_loadbalancers_3131(self, method, url, body, headers): + """ update_balancer(b, port=443), then get_balancer('3131') """ + if method == "PUT": + self.assertEqual(json.loads(body), {'port': 1337}) + return (httplib.ACCEPTED, "", {}, httplib.responses[httplib.ACCEPTED]) + elif method == "GET": + response_body = json.loads(self.fixtures.load("v1_slug_loadbalancers_3xxx.json")) + response_body['loadBalancer']['id'] = 3131 + response_body['loadBalancer']['port'] = 1337 + return (httplib.OK, json.dumps(response_body), {}, httplib.responses[httplib.OK]) + raise NotImplementedError + + def _v1_0_slug_loadbalancers_3132(self, method, url, body, headers): + """ update_balancer(b, name='new_lb_name'), then get_balancer('3132') """ + if method == "PUT": + self.assertEqual(json.loads(body), {'name': 'new_lb_name'}) + return (httplib.ACCEPTED, "", {}, httplib.responses[httplib.ACCEPTED]) + elif method == "GET": + response_body = json.loads(self.fixtures.load("v1_slug_loadbalancers_3xxx.json")) + response_body['loadBalancer']['id'] = 3132 + response_body['loadBalancer']['name'] = 'new_lb_name' + return (httplib.OK, json.dumps(response_body), {}, httplib.responses[httplib.OK]) + raise NotImplementedError + + def _v1_0_slug_loadbalancers_3133(self, method, url, body, headers): + """ update_balancer(b, algorithm='ROUND_ROBIN'), then get_balancer('3133') """ + if method == "PUT": + self.assertEqual(json.loads(body), {'algorithm': 'ROUND_ROBIN'}) + return (httplib.ACCEPTED, "", {}, httplib.responses[httplib.ACCEPTED]) + elif method == "GET": + response_body = json.loads(self.fixtures.load("v1_slug_loadbalancers_3xxx.json")) + response_body['loadBalancer']['id'] = 3133 + response_body['loadBalancer']['algorithm'] = 'ROUND_ROBIN' + return (httplib.OK, json.dumps(response_body), {}, httplib.responses[httplib.OK]) + raise NotImplementedError + + def _v1_0_slug_loadbalancers_3134(self, method, url, body, headers): + """ update.balancer(b, algorithm='HAVE_MERCY_ON_OUR_SERVERS') """ + if method == "PUT": + return (httplib.BAD_REQUEST, "", {}, httplib.responses[httplib.BAD_REQUEST]) + raise NotImplementedError + def _v1_1_auth(self, method, url, body, headers): headers = {'content-type': 'application/json; charset=UTF-8'} body = self.auth_fixtures.load('_v1_1__auth.json')
