From 208b86e962aa3fbf0d44d6a43d530c6609c167eb Mon Sep 17 00:00:00 2001 From: Ivan Kusalic Date: Thu, 22 Aug 2013 17:03:06 +0200 Subject: [PATCH] Add support for floating IPs to OpenStack --- libcloud/compute/drivers/openstack.py | 150 +++++++++++++++++++++ .../fixtures/openstack_v1.1/_floating_ip.json | 1 + .../openstack_v1.1/_floating_ip_pools.json | 1 + .../fixtures/openstack_v1.1/_floating_ips.json | 1 + libcloud/test/compute/test_openstack.py | 106 ++++++++++++++- 5 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 libcloud/test/compute/fixtures/openstack_v1.1/_floating_ip.json create mode 100644 libcloud/test/compute/fixtures/openstack_v1.1/_floating_ip_pools.json create mode 100644 libcloud/test/compute/fixtures/openstack_v1.1/_floating_ips.json diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index 963a280..7d2a889 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -51,6 +51,8 @@ __all__ = [ 'OpenStack_1_1_Response', 'OpenStack_1_1_Connection', 'OpenStack_1_1_NodeDriver', + 'OpenStack_1_1_FloatingIpPool', + 'OpenStack_1_1_FloatingIpAddress', 'OpenStackNodeDriver' ] @@ -1805,3 +1807,151 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): """ resp = self._node_action(node, 'unrescue') return resp.status == httplib.ACCEPTED + + def _to_floating_ip_pools(self, obj): + pool_elements = obj['floating_ip_pools'] + return [self._to_floating_ip_pool(pool) for pool in pool_elements] + + def _to_floating_ip_pool(self, obj): + return OpenStack_1_1_FloatingIpPool(obj['name'], self.connection) + + def ex_list_floating_ip_pools(self): + """ + List available floating IP pools + + @rtype: C{list} of L{OpenStack_1_1_FloatingIpPool} + """ + return self._to_floating_ip_pools( + self.connection.request('/os-floating-ip-pools').object) + + def ex_attach_floating_ip_to_node(self, node, ip): + """ + Attach the floating IP to the node + + @param node: node + @type node: L{Node} + + @param ip: floating IP to attach + @type ip: C{str} or L{OpenStack_1_1_FloatingIpAddress} + + @rtype: C{bool} + """ + address = ip.ip_address if hasattr(ip, 'ip_address') else ip + data = { + 'addFloatingIp': { 'address': address } + } + resp = self.connection.request('/servers/%s/action' % node.id, + method='POST', data=data) + return resp.status == httplib.ACCEPTED + + def ex_detach_floating_ip_from_node(self, node, ip): + """ + Detach the floating IP from the node + + @param node: node + @type node: L{Node} + + @param ip: floating IP to remove + @type ip: C{str} or L{OpenStack_1_1_FloatingIpAddress} + + @rtype: C{bool} + """ + address = ip.ip_address if hasattr(ip, 'ip_address') else ip + data = { + 'removeFloatingIp': { 'address': address } + } + resp = self.connection.request('/servers/%s/action' % node.id, + method='POST', data=data) + return resp.status == httplib.ACCEPTED + + +class OpenStack_1_1_FloatingIpPool(object): + """ + Floating IP Pool info. + """ + + def __init__(self, name, connection): + self.name = name + self.connection = connection + + def list_floating_ips(self): + """ + List floating IPs in the pool + + @rtype: C{list} of L{OpenStack_1_1_FloatingIpAddress} + """ + return self._to_floating_ips( + self.connection.request('/os-floating-ips').object) + + def _to_floating_ips(self, obj): + ip_elements = obj['floating_ips'] + return [self._to_floating_ip(ip) for ip in ip_elements] + + def _to_floating_ip(self, obj): + return OpenStack_1_1_FloatingIpAddress(obj['id'], obj['ip'], self, + obj['instance_id']) + + def get_floating_ip(self, ip): + """ + Get specified floating IP from the pool + + @param ip: floating IP to remove + @type ip: C{str} + + @rtype: L{OpenStack_1_1_FloatingIpAddress} + """ + ip_obj, = [x for x in self.list_floating_ips() if x.ip_address == ip] + return ip_obj + + def create_floating_ip(self): + """ + Create new floating IP in the pool + + @rtype: L{OpenStack_1_1_FloatingIpAddress} + """ + resp = self.connection.request('/os-floating-ips', + method='POST', data={ 'pool': self.name }) + data = resp.object['floating_ip'] + id = data['id'] + ip_address = data['ip'] + return OpenStack_1_1_FloatingIpAddress(id, ip_address, self) + + def delete_floating_ip(self, ip): + """ + Delete specified floating IP from the pool + + @param ip: floating IP to remove + @type ip:L{OpenStack_1_1_FloatingIpAddress} + + @rtype: C{bool} + """ + resp = self.connection.request('/os-floating-ips/%s' % ip.id, + method='DELETE') + return resp.status in (httplib.NO_CONTENT, httplib.ACCEPTED) + + def __repr__(self): + return ('' % self.name) + + +class OpenStack_1_1_FloatingIpAddress(object): + """ + Floating IP info. + """ + + def __init__(self, id, ip_address, pool, node_id=None): + self.id = str(id) + self.ip_address = ip_address + self.pool = pool + self.node_id = node_id + + def delete(self): + """ + Delete this floating IP + + @rtype: C{bool} + """ + return self.pool.delete_floating_ip(self) + + def __repr__(self): + return ('' + % (self.id, self.ip_address, self.pool)) diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_floating_ip.json b/libcloud/test/compute/fixtures/openstack_v1.1/_floating_ip.json new file mode 100644 index 0000000..7825a48 --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1.1/_floating_ip.json @@ -0,0 +1 @@ +{"floating_ip": {"instance_id": null, "ip": "10.3.1.42", "fixed_ip": null, "id": "09ea1784-2f81-46dc-8c91-244b4df75bde", "pool": "public"}} diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_floating_ip_pools.json b/libcloud/test/compute/fixtures/openstack_v1.1/_floating_ip_pools.json new file mode 100644 index 0000000..8961f51 --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1.1/_floating_ip_pools.json @@ -0,0 +1 @@ +{"floating_ip_pools": [{"name": "public"}, {"name": "foobar"}]} diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_floating_ips.json b/libcloud/test/compute/fixtures/openstack_v1.1/_floating_ips.json new file mode 100644 index 0000000..82a0157 --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1.1/_floating_ips.json @@ -0,0 +1 @@ +{"floating_ips": [{"instance_id": null, "ip": "10.3.1.42", "fixed_ip": null, "id": "09ea1784-2f81-46dc-8c91-244b4df75bde", "pool": "public"}, {"instance_id": "fcfc96da-19e2-40fd-8497-f29da1b21143", "ip": "10.3.1.1", "fixed_ip": "172.16.21.4", "id": "04c5336a-0629-4694-ba30-04b0bdfa88a4", "pool": "public"}]} diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py index 730dced..3dc4cab 100644 --- a/libcloud/test/compute/test_openstack.py +++ b/libcloud/test/compute/test_openstack.py @@ -37,7 +37,9 @@ from libcloud.compute.types import Provider from libcloud.compute.providers import get_driver from libcloud.compute.drivers.openstack import ( OpenStack_1_0_NodeDriver, OpenStack_1_0_Response, - OpenStack_1_1_NodeDriver, OpenStackSecurityGroup, OpenStackSecurityGroupRule + OpenStack_1_1_NodeDriver, OpenStackSecurityGroup, + OpenStackSecurityGroupRule, OpenStack_1_1_FloatingIpPool, + OpenStack_1_1_FloatingIpAddress ) from libcloud.compute.base import Node, NodeImage, NodeSize, StorageVolume from libcloud.pricing import set_pricing, clear_pricing_data @@ -1140,6 +1142,75 @@ class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin): result = self.driver.ex_delete_security_group_rule(security_group_rule) self.assertTrue(result) + def test_ex_list_floating_ip_pools(self): + ret = self.driver.ex_list_floating_ip_pools() + self.assertEqual(ret[0].name, 'public') + self.assertEqual(ret[1].name, 'foobar') + + def test_ex_attach_floating_ip_to_node(self): + image = NodeImage(id=11, name='Ubuntu 8.10 (intrepid)', driver=self.driver) + size = NodeSize(1, '256 slice', None, None, None, None, driver=self.driver) + node = self.driver.create_node(name='racktest', image=image, size=size) + node.id = 4242 + ip = '42.42.42.42' + + self.assertTrue(self.driver.ex_attach_floating_ip_to_node(node, ip)) + + def test_detach_floating_ip_from_node(self): + image = NodeImage(id=11, name='Ubuntu 8.10 (intrepid)', driver=self.driver) + size = NodeSize(1, '256 slice', None, None, None, None, driver=self.driver) + node = self.driver.create_node(name='racktest', image=image, size=size) + node.id = 4242 + ip = '42.42.42.42' + + self.assertTrue(self.driver.ex_detach_floating_ip_from_node(node, ip)) + + def test_OpenStack_1_1_FloatingIpPool_list_floating_ips(self): + pool = OpenStack_1_1_FloatingIpPool('foo', self.driver.connection) + ret = pool.list_floating_ips() + + self.assertEqual(ret[0].id, '09ea1784-2f81-46dc-8c91-244b4df75bde') + self.assertEqual(ret[0].pool, pool) + self.assertEqual(ret[0].ip_address, '10.3.1.42') + self.assertEqual(ret[0].node_id, None) + self.assertEqual(ret[1].id, '04c5336a-0629-4694-ba30-04b0bdfa88a4') + self.assertEqual(ret[1].pool, pool) + self.assertEqual(ret[1].ip_address, '10.3.1.1') + self.assertEqual(ret[1].node_id, 'fcfc96da-19e2-40fd-8497-f29da1b21143') + + def test_OpenStack_1_1_FloatingIpPool_get_floating_ip(self): + pool = OpenStack_1_1_FloatingIpPool('foo', self.driver.connection) + ret = pool.get_floating_ip('10.3.1.42') + + self.assertEqual(ret.id, '09ea1784-2f81-46dc-8c91-244b4df75bde') + self.assertEqual(ret.pool, pool) + self.assertEqual(ret.ip_address, '10.3.1.42') + self.assertEqual(ret.node_id, None) + + def test_OpenStack_1_1_FloatingIpPool_create_floating_ip(self): + pool = OpenStack_1_1_FloatingIpPool('foo', self.driver.connection) + ret = pool.create_floating_ip() + + self.assertEqual(ret.id, '09ea1784-2f81-46dc-8c91-244b4df75bde') + self.assertEqual(ret.pool, pool) + self.assertEqual(ret.ip_address, '10.3.1.42') + self.assertEqual(ret.node_id, None) + + def test_OpenStack_1_1_FloatingIpPool_delete_floating_ip(self): + pool = OpenStack_1_1_FloatingIpPool('foo', self.driver.connection) + ip = OpenStack_1_1_FloatingIpAddress('foo-bar-id', '42.42.42.42', pool) + + self.assertTrue(pool.delete_floating_ip(ip)) + + def test_OpenStack_1_1_FloatingIpAddress_delete(self): + pool = OpenStack_1_1_FloatingIpPool('foo', self.driver.connection) + pool.delete_floating_ip = Mock() + ip = OpenStack_1_1_FloatingIpAddress('foo-bar-id', '42.42.42.42', pool) + + ip.pool.delete_floating_ip() + + self.assertEqual(pool.delete_floating_ip.call_count, 1) + class OpenStack_1_1_FactoryMethodTests(OpenStack_1_1_Tests): should_list_locations = False @@ -1369,6 +1440,39 @@ class OpenStack_1_1_MockHttp(MockHttpTestCase): return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + def _v1_1_slug_os_floating_ip_pools(self, method, url, body, headers): + if method == "GET": + body = self.fixtures.load('_floating_ip_pools.json') + return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + else: + raise NotImplementedError() + + def _v1_1_slug_os_floating_ips_foo_bar_id(self, method, url, body, headers): + if method == "DELETE": + body = '' + return (httplib.ACCEPTED, body, self.json_content_headers, httplib.responses[httplib.OK]) + else: + raise NotImplementedError() + + def _v1_1_slug_os_floating_ips(self, method, url, body, headers): + if method == "GET": + body = self.fixtures.load('_floating_ips.json') + return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + elif method == "POST": + body = self.fixtures.load('_floating_ip.json') + return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + else: + raise NotImplementedError() + + def _v1_1_slug_servers_4242_action(self, method, url, body, headers): + if method == "POST": + body = '' + return (httplib.ACCEPTED, body, self.json_content_headers, httplib.responses[httplib.OK]) + else: + raise NotImplementedError() + + return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + # This exists because the nova compute url in devstack has v2 in there but the v1.1 fixtures # work fine. -- 1.8.2.3