diff --git a/libcloud/loadbalancer/drivers/rackspace.py b/libcloud/loadbalancer/drivers/rackspace.py index 7f33634..be25f76 100644 --- a/libcloud/loadbalancer/drivers/rackspace.py +++ b/libcloud/loadbalancer/drivers/rackspace.py @@ -338,17 +338,29 @@ def create_balancer(self, name, members, protocol='http', algorithm=algorithm) balancer_attrs.update({ - "virtualIps": [{"type": "PUBLIC"}], - "nodes": [{"address": member.ip, - "port": member.port, - "condition": "ENABLED"} for member in members], + 'virtualIps': [{'type': 'PUBLIC'}], + 'nodes': [self._member_attributes(member) 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"]) + return self._to_balancer(resp.object['loadBalancer']) + + def _member_attributes(self, member): + member_attributes = {'address': member.ip, + 'port': member.port} + + member_attributes.update(self._kwargs_to_mutable_member_attrs( + **member.extra)) + + # If the condition is not specified on the member, then it should be set + # to ENABLED by default + if 'condition' not in member_attributes: + member_attributes['condition'] = 'ENABLED' + + return member_attributes def destroy_balancer(self, balancer): uri = '/loadbalancers/%s' % (balancer.id) @@ -380,20 +392,31 @@ def get_balancer(self, balancer_id): 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"}] - } + member_object = {"nodes": [self._member_attributes(member)]} 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 ex_balancer_attach_members(self, balancer, members): + """ + Attaches a list of members to a load balancer. + + @param balancer: The Balancer to which members will be attached. + @type balancer: C{Balancer} + + @param members: A list of Members to attach. + @type members: C{list} + """ + member_objects = {"nodes": [self._member_attributes(member) for member + in members]} + + uri = '/loadbalancers/%s/nodes' % (balancer.id) + resp = self.connection.request(uri, method='POST', + data=json.dumps(member_objects)) + return self._to_members(resp.object) + 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 diff --git a/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_8291.json b/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_8291.json new file mode 100644 index 0000000..b623f46 --- /dev/null +++ b/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_8291.json @@ -0,0 +1,56 @@ +{ + "loadBalancer": { + "algorithm": "RANDOM", + "cluster": { + "name": "ztm-n05.lbaas.ord1.rackspace.net" + }, + "connectionLogging": { + "enabled": false + }, + "created": { + "time": "2011-04-07T16:27:50Z" + }, + "id": 8291, + "name": "test8291", + "nodes": [ + { + "address": "10.1.0.11", + "condition": "ENABLED", + "id": 30944, + "port": 80, + "status": "ONLINE", + "weight": 12 + }, + { + "address": "10.1.0.10", + "condition": "DISABLED", + "id": 30945, + "port": 80, + "status": "OFFLINE", + "weight": 8 + }, + { + "address": "10.1.0.9", + "condition": "DRAINING", + "id": 30946, + "port": 8080, + "status": "DRAINING", + "weight": 20 + } + ], + "port": 80, + "protocol": "HTTP", + "status": "ACTIVE", + "updated": { + "time": "2011-04-07T16:28:12Z" + }, + "virtualIps": [ + { + "address": "1.1.1.1", + "id": 1151, + "ipVersion": "IPV4", + "type": "PUBLIC" + } + ] + } +} diff --git a/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_8292.json b/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_8292.json new file mode 100644 index 0000000..c62a3dc --- /dev/null +++ b/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_8292.json @@ -0,0 +1,56 @@ +{ + "loadBalancer": { + "algorithm": "RANDOM", + "cluster": { + "name": "ztm-n05.lbaas.ord1.rackspace.net" + }, + "connectionLogging": { + "enabled": false + }, + "created": { + "time": "2011-04-07T16:27:50Z" + }, + "id": 8292, + "name": "test8292", + "nodes": [ + { + "address": "10.1.0.11", + "condition": "ENABLED", + "id": 30944, + "port": 80, + "status": "ONLINE", + "weight": 12 + }, + { + "address": "10.1.0.10", + "condition": "DISABLED", + "id": 30945, + "port": 80, + "status": "OFFLINE", + "weight": 8 + }, + { + "address": "10.1.0.9", + "condition": "DRAINING", + "id": 30946, + "port": 8080, + "status": "DRAINING", + "weight": 20 + } + ], + "port": 80, + "protocol": "HTTP", + "status": "ACTIVE", + "updated": { + "time": "2011-04-07T16:28:12Z" + }, + "virtualIps": [ + { + "address": "1.1.1.2", + "id": 1151, + "ipVersion": "IPV4", + "type": "PUBLIC" + } + ] + } +} diff --git a/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_8292_nodes_post.json b/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_8292_nodes_post.json new file mode 100644 index 0000000..e127c82 --- /dev/null +++ b/test/loadbalancer/fixtures/rackspace/v1_slug_loadbalancers_8292_nodes_post.json @@ -0,0 +1,20 @@ +{ + "nodes": [ + { + "address": "10.1.0.12", + "condition": "ENABLED", + "id": 30972, + "port": 80, + "status": "ONLINE", + "weight": 1 + }, + { + "address": "10.1.0.13", + "condition": "ENABLED", + "id": 30973, + "port": 80, + "status": "ONLINE", + "weight": 1 + } + ] +} diff --git a/test/loadbalancer/test_rackspace.py b/test/loadbalancer/test_rackspace.py index 55a59dd..3af7b2a 100644 --- a/test/loadbalancer/test_rackspace.py +++ b/test/loadbalancer/test_rackspace.py @@ -103,8 +103,9 @@ def test_create_balancer(self): balancer = self.driver.create_balancer(name='test2', port=80, algorithm=Algorithm.ROUND_ROBIN, - members=(Member(None, '10.1.0.10', 80), - Member(None, '10.1.0.11', 80)) + members=(Member(None, '10.1.0.10', 80, extra={'condition': MemberCondition.DISABLED, + 'weight': 10}), + Member(None, '10.1.0.11', 80)) ) self.assertEquals(balancer.name, 'test2') @@ -596,12 +597,37 @@ def test_balancer_members_extra_status(self): def test_balancer_attach_member(self): balancer = self.driver.get_balancer(balancer_id='8290') + extra = {'condition': MemberCondition.DISABLED, + 'weight': 10} + member = balancer.attach_member(Member(None, ip='10.1.0.12', + port='80', extra=extra)) + + self.assertEquals(member.ip, '10.1.0.12') + self.assertEquals(member.port, 80) + + def test_balancer_attach_member_with_no_condition_specified(self): + balancer = self.driver.get_balancer(balancer_id='8291') member = balancer.attach_member(Member(None, ip='10.1.0.12', port='80')) self.assertEquals(member.ip, '10.1.0.12') self.assertEquals(member.port, 80) + def test_balancer_attach_members(self): + balancer = self.driver.get_balancer(balancer_id='8292') + members = [Member(None, ip='10.1.0.12', port='80'), + Member(None, ip='10.1.0.13', port='80')] + + attached_members = self.driver.ex_balancer_attach_members(balancer, + members) + + first_member = attached_members[0] + second_member = attached_members[1] + self.assertEquals(first_member.ip, '10.1.0.12') + self.assertEquals(first_member.port, 80) + self.assertEquals(second_member.ip, '10.1.0.13') + self.assertEquals(second_member.port, 80) + def test_balancer_detach_member(self): balancer = self.driver.get_balancer(balancer_id='8290') member = balancer.list_members()[0] @@ -796,8 +822,14 @@ def _v1_0_slug_loadbalancers(self, method, url, body, headers): return (httplib.OK, body, {}, httplib.responses[httplib.OK]) elif method == "POST": body_json = json.loads(body) - self.assertEqual(body_json['loadBalancer']['protocol'], 'HTTP') - self.assertEqual(body_json['loadBalancer']['algorithm'], 'ROUND_ROBIN') + loadbalancer_json = body_json['loadBalancer'] + member_1_json, member_2_json = loadbalancer_json['nodes'] + + self.assertEqual(loadbalancer_json['protocol'], 'HTTP') + self.assertEqual(loadbalancer_json['algorithm'], 'ROUND_ROBIN') + self.assertEqual(member_1_json['condition'], 'DISABLED') + self.assertEqual(member_1_json['weight'], 10) + self.assertEqual(member_2_json['condition'], 'ENABLED') body = self.fixtures.load('v1_slug_loadbalancers_post.json') return (httplib.ACCEPTED, body, {}, @@ -837,8 +869,12 @@ def _v1_0_slug_loadbalancers_8290_nodes(self, method, url, body, headers): body = self.fixtures.load('v1_slug_loadbalancers_8290_nodes.json') return (httplib.OK, body, {}, httplib.responses[httplib.OK]) elif method == "POST": - body = self.fixtures.load('v1_slug_loadbalancers_8290_nodes_post.json') - return (httplib.ACCEPTED, body, {}, + json_body = json.loads(body) + json_node = json_body['nodes'][0] + self.assertEqual('DISABLED', json_node['condition']) + self.assertEqual(10, json_node['weight']) + response_body = self.fixtures.load('v1_slug_loadbalancers_8290_nodes_post.json') + return (httplib.ACCEPTED, response_body, {}, httplib.responses[httplib.ACCEPTED]) elif method == "DELETE": nodes = self.fixtures.load('v1_slug_loadbalancers_8290_nodes.json') @@ -853,6 +889,44 @@ def _v1_0_slug_loadbalancers_8290_nodes(self, method, url, body, headers): raise NotImplementedError + def _v1_0_slug_loadbalancers_8291(self, method, url, body, headers): + if method == "GET": + body = self.fixtures.load('v1_slug_loadbalancers_8291.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + raise NotImplementedError + + def _v1_0_slug_loadbalancers_8291_nodes(self, method, url, body, headers): + if method == "POST": + json_body = json.loads(body) + json_node = json_body['nodes'][0] + self.assertEqual('ENABLED', json_node['condition']) + response_body = self.fixtures.load('v1_slug_loadbalancers_8290_nodes_post.json') + return (httplib.ACCEPTED, response_body, {}, + httplib.responses[httplib.ACCEPTED]) + + raise NotImplementedError + + def _v1_0_slug_loadbalancers_8292(self, method, url, body, headers): + if method == "GET": + body = self.fixtures.load('v1_slug_loadbalancers_8292.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + raise NotImplementedError + + def _v1_0_slug_loadbalancers_8292_nodes(self, method, url, body, headers): + if method == "POST": + json_body = json.loads(body) + json_node_1 = json_body['nodes'][0] + json_node_2 = json_body['nodes'][1] + self.assertEqual('10.1.0.12', json_node_1['address']) + self.assertEqual('10.1.0.13', json_node_2['address']) + response_body = self.fixtures.load('v1_slug_loadbalancers_8292_nodes_post.json') + return (httplib.ACCEPTED, response_body, {}, + httplib.responses[httplib.ACCEPTED]) + + raise NotImplementedError + def _v1_0_slug_loadbalancers_8290_nodes_30944(self, method, url, body, headers): if method == "PUT": json_body = json.loads(body)