Impleent floating ip methods @compute/cyclades
authorStavros Sachtouris <saxtouri@admin.grnet.gr>
Wed, 29 May 2013 14:41:04 +0000 (17:41 +0300)
committerStavros Sachtouris <saxtouri@admin.grnet.gr>
Wed, 29 May 2013 14:41:04 +0000 (17:41 +0300)
Refs: #3862

New methods introduced and unit-tested:
 ComputeRestClient: floating_ip_pools_get, floating_ips_get/post/delete
 CycladesRestClient: floating_ip_pools_get, floating_ips_get/post/delete
 ComputeClient: get_floating_ip_pools, get_floating_ips,
    alloc/get_delete_floating_ip
 CycladesClient: get_floating_ip_pools, get_floating_ips,
    alloc/get_delete_floating_ip, dis/assoc_floating_ip_to_server

Changelog
kamaki/clients/compute/__init__.py
kamaki/clients/compute/rest_api.py
kamaki/clients/compute/test.py
kamaki/clients/cyclades/__init__.py
kamaki/clients/cyclades/rest_api.py
kamaki/clients/cyclades/test.py

index 3dffa93..2456110 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -5,5 +5,11 @@ Bug Fixes:
 Changes:
 
 Features:
-
+- Implement floating ip methods for compute and cyclades clients [#3862]
+ ComputeRestClient: floating_ip_pools_get, floating_ips_get/post/delete
+ CycladesRestClient: floating_ip_pools_get, floating_ips_get/post/delete
+ ComputeClient: get_floating_ip_pools, get_floating_ips,
+    alloc/get_delete_floating_ip
+ CycladesClient: get_floating_ip_pools, get_floating_ips,
+    alloc/get_delete_floating_ip, dis/assoc_floating_ip_to_server
 
index 3cfbeb8..2464e98 100644 (file)
@@ -297,3 +297,61 @@ class ComputeClient(ComputeRestClient):
         command = path4url('metadata', key)
         r = self.images_delete(image_id, command)
         return r.headers
+
+    def get_floating_ip_pools(self, tenant_id):
+        """
+        :param tenant_id: (str)
+
+        :returns: (dict) {floating_ip_pools:[{name: ...}, ...]}
+        """
+        r = self.floating_ip_pools_get(tenant_id)
+        return r.json
+
+    def get_floating_ips(self, tenant_id):
+        """
+        :param tenant_id: (str)
+
+        :returns: (dict) {floating_ips:[
+            {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
+            ... ]}
+        """
+        r = self.floating_ips_get(tenant_id)
+        return r.json
+
+    def alloc_floating_ip(self, tenant_id, pool=None):
+        """
+        :param tenant_id: (str)
+
+        :param pool: (str) pool of ips to allocate from
+
+        :returns: (dict) {
+                fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...
+            }
+        """
+        json_data = dict(pool=pool) if pool else dict()
+        r = self.floating_ips_post(tenant_id, json_data)
+        return r.json['floating_ip']
+
+    def get_floating_ip(self, tenant_id, fip_id=None):
+        """
+        :param tenant_id: (str)
+
+        :param fip_id: (str) floating ip id (if None, all ips are returned)
+
+        :returns: (list) [
+            {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
+            ... ]
+        """
+        r = self.floating_ips_get(tenant_id, fip_id)
+        return r.json['floating_ips']
+
+    def delete_floating_ip(self, tenant_id, fip_id=None):
+        """
+        :param tenant_id: (str)
+
+        :param fip_id: (str) floating ip id (if None, all ips are deleted)
+
+        :returns: (dict) request headers
+        """
+        r = self.floating_ips_delete(tenant_id, fip_id)
+        return r.headers
index 6172ce2..fb3b7a3 100644 (file)
@@ -242,22 +242,19 @@ class ComputeRestClient(Client):
         path = path4url(tenant_id, 'os-floating-ip-pools')
         return self.get(path, success=success, **kwargs)
 
-    def floating_ips_get(self, tenant_id, success=200, **kwargs):
-        path = path4url(tenant_id, 'os-floating-ips')
+    def floating_ips_get(self, tenant_id, ip='', success=200, **kwargs):
+        path = path4url(tenant_id, 'os-floating-ips', ip or '')
         return self.get(path, success=success, **kwargs)
 
-    def floating_ips_post(self, tenant_id, json_data, success=201, **kwargs):
-        path = path4url(tenant_id, 'os-floating-ips')
+    def floating_ips_post(
+            self, tenant_id, json_data, ip='', success=201, **kwargs):
+        path = path4url(tenant_id, 'os-floating-ips', ip or '')
         if json_data is not None:
             json_data = json.dumps(json_data)
             self.set_header('Content-Type', 'application/json')
             self.set_header('Content-Length', len(json_data))
         return self.post(path, data=json_data, success=success, **kwargs)
 
-    def floating_ip_get(self, tenant_id, success=200, **kwargs):
-        path = path4url(tenant_id, 'os-floating-ip')
-        return self.get(path, success=success, **kwargs)
-
-    def floating_ip_delete(self, tenant_id, success=204, **kwargs):
-        path = path4url(tenant_id, 'os-floating-ip')
+    def floating_ips_delete(self, tenant_id, ip='', success=204, **kwargs):
+        path = path4url(tenant_id, 'os-floating-ips', ip or '')
         return self.delete(path, success=success, **kwargs)
index 819aa01..c0d32ed 100644 (file)
@@ -244,13 +244,15 @@ class ComputeRestClient(TestCase):
     def test_floating_ips_get(self, get):
         for args in product(
                 ('tenant1', 'tenant2'),
+                ('', '192.193.194.195'),
                 (200, 204),
                 ({}, {'k': 'v'})):
-            tenant_id, success, kwargs = args
-            r = self.client.floating_ips_get(tenant_id, success, **kwargs)
+            tenant_id, ip, success, kwargs = args
+            r = self.client.floating_ips_get(*args[:3], **kwargs)
             self.assertTrue(isinstance(r, FR))
+            expected = '' if not ip else '/%s' % ip
             self.assertEqual(get.mock_calls[-1], call(
-                '/%s/os-floating-ips' % tenant_id,
+                '/%s/os-floating-ips%s' % (tenant_id, expected),
                 success=success, **kwargs))
 
     @patch('%s.set_header' % rest_pkg)
@@ -259,44 +261,35 @@ class ComputeRestClient(TestCase):
         for args in product(
                 ('tenant1', 'tenant2'),
                 (None, [dict(json="data"), dict(data="json")]),
+                ('', '192.193.194.195'),
                 (202, 204),
                 ({}, {'k': 'v'})):
-            (tenant_id, json_data, success, kwargs) = args
-            self.client.floating_ips_post(*args[:3], **kwargs)
+            (tenant_id, json_data, ip, success, kwargs) = args
+            self.client.floating_ips_post(*args[:4], **kwargs)
             if json_data:
                 json_data = dumps(json_data)
                 self.assertEqual(SH.mock_calls[-2:], [
                     call('Content-Type', 'application/json'),
                     call('Content-Length', len(json_data))])
+            expected = '' if not ip else '/%s' % ip
             self.assertEqual(post.mock_calls[-1], call(
-                '/%s/os-floating-ips' % tenant_id,
+                '/%s/os-floating-ips%s' % (tenant_id, expected),
                 data=json_data, success=success,
                 **kwargs))
 
-    @patch('%s.get' % rest_pkg, return_value=FR())
-    def test_floating_ip_get(self, get):
-        for args in product(
-                ('tenant1', 'tenant2'),
-                (200, 204),
-                ({}, {'k': 'v'})):
-            tenant_id, success, kwargs = args
-            r = self.client.floating_ip_get(tenant_id, success, **kwargs)
-            self.assertTrue(isinstance(r, FR))
-            self.assertEqual(get.mock_calls[-1], call(
-                '/%s/os-floating-ip' % tenant_id,
-                success=success, **kwargs))
-
     @patch('%s.delete' % rest_pkg, return_value=FR())
-    def test_floating_ip_delete(self, delete):
+    def test_floating_ips_delete(self, delete):
         for args in product(
                 ('tenant1', 'tenant2'),
+                ('', '192.193.194.195'),
                 (204,),
                 ({}, {'k': 'v'})):
-            tenant_id, success, kwargs = args
-            r = self.client.floating_ip_delete(tenant_id, success, **kwargs)
+            tenant_id, ip, success, kwargs = args
+            r = self.client.floating_ips_delete(*args[:3], **kwargs)
             self.assertTrue(isinstance(r, FR))
+            expected = '' if not ip else '/%s' % ip
             self.assertEqual(delete.mock_calls[-1], call(
-                '/%s/os-floating-ip' % tenant_id,
+                '/%s/os-floating-ips%s' % (tenant_id, expected),
                 success=success, **kwargs))
 
 
@@ -508,6 +501,63 @@ class ComputeClient(TestCase):
         self.client.delete_image_metadata(img_ref, key)
         ID.assert_called_once_with(img_ref, '/metadata/%s' % key)
 
+    @patch('%s.floating_ip_pools_get' % compute_pkg, return_value=FR())
+    def test_get_floating_ip_pools(self, get):
+        tid = 't3n@nt_1d'
+        r = self.client.get_floating_ip_pools(tid)
+        self.assert_dicts_are_equal(r, FR.json)
+        self.assertEqual(get.mock_calls[-1], call(tid))
+
+    @patch('%s.floating_ips_get' % compute_pkg, return_value=FR())
+    def test_get_floating_ips(self, get):
+        tid = 't3n@nt_1d'
+        r = self.client.get_floating_ips(tid)
+        self.assert_dicts_are_equal(r, FR.json)
+        self.assertEqual(get.mock_calls[-1], call(tid))
+
+    @patch('%s.floating_ips_post' % compute_pkg, return_value=FR())
+    def test_alloc_floating_ip(self, post):
+        FR.json = dict(floating_ip=dict(
+            fixed_ip='fip',
+            id=1,
+            instance_id='lala',
+            ip='102.0.0.1',
+            pool='pisine'))
+        for args in product(
+                ('t1', 't2'),
+                (None, 'pisine')):
+            r = self.client.alloc_floating_ip(*args)
+            tenant_id, pool = args
+            self.assert_dicts_are_equal(r, FR.json['floating_ip'])
+            expected = dict(pool=pool) if pool else dict()
+            self.assertEqual(post.mock_calls[-1], call(tenant_id, expected))
+
+    @patch('%s.floating_ips_get' % compute_pkg, return_value=FR())
+    def test_get_floating_ip(self, get):
+        FR.json = dict(floating_ips=[dict(
+            fixed_ip='fip',
+            id=1,
+            instance_id='lala',
+            ip='102.0.0.1',
+            pool='pisine'), ])
+        for args in product(
+                ('t1', 't2'),
+                (None, 'fip')):
+            r = self.client.get_floating_ip(*args)
+            tenant_id, fip = args
+            self.assertEqual(r, FR.json['floating_ips'])
+            self.assertEqual(get.mock_calls[-1], call(tenant_id, fip))
+
+    @patch('%s.floating_ips_delete' % compute_pkg, return_value=FR())
+    def test_delete_floating_ip(self, delete):
+        for args in product(
+                ('t1', 't2'),
+                (None, 'fip')):
+            r = self.client.delete_floating_ip(*args)
+            tenant_id, fip = args
+            self.assertEqual(r, FR.headers)
+            self.assertEqual(delete.mock_calls[-1], call(tenant_id, fip))
+
 
 if __name__ == '__main__':
     from sys import argv
index 28b56d6..de4cbeb 100644 (file)
@@ -316,3 +316,106 @@ class CycladesClient(CycladesRestClient):
                     pass
             return r['status']
         return False
+
+    def get_floating_ip_pools(self):
+        """
+        :returns: (dict) {floating_ip_pools:[{name: ...}, ...]}
+        """
+        r = self.floating_ip_pools_get()
+        return r.json
+
+    def get_floating_ips(self):
+        """
+        :returns: (dict) {floating_ips:[
+            {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
+            ... ]}
+        """
+        r = self.floating_ips_get()
+        return r.json
+
+    def alloc_floating_ip(self, pool=None, address=None):
+        """
+        :param pool: (str) pool of ips to allocate from
+
+        :param address: (str) ip address to request
+
+        :returns: (dict) {
+                fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...
+            }
+        """
+        json_data = dict()
+        if pool:
+            json_data['pool'] = pool
+            if address:
+                json_data['address'] = address
+        r = self.floating_ips_post(json_data)
+        return r.json['floating_ip']
+
+    def get_floating_ip(self, fip_id):
+        """
+        :param fip_id: (str) floating ip id
+
+        :returns: (dict)
+            {fixed_ip: ..., id: ..., instance_id: ..., ip: ..., pool: ...},
+
+        :raises AssertionError: if fip_id is emtpy
+        """
+        assert fip_id, 'floating ip id is needed for get_floating_ip'
+        r = self.floating_ips_get(fip_id)
+        return r.json['floating_ip']
+
+    def delete_floating_ip(self, fip_id=None):
+        """
+        :param fip_id: (str) floating ip id (if None, all ips are deleted)
+
+        :returns: (dict) request headers
+
+        :raises AssertionError: if fip_id is emtpy
+        """
+        assert fip_id, 'floating ip id is needed for delete_floating_ip'
+        r = self.floating_ips_delete(fip_id)
+        return r.headers
+
+    def assoc_floating_ip_to_server(self, server_id, address):
+        """Associate the address ip to server with server_id
+
+        :param server_id: (int)
+
+        :param address: (str) the ip address to assign to server (vm)
+
+        :returns: (dict) request headers
+
+        :raises ValueError: if server_id cannot be converted to int
+
+        :raises ValueError: if server_id is not of a int-convertable type
+
+        :raises AssertionError: if address is emtpy
+        """
+        server_id = int(server_id)
+        assert address, 'address is needed for assoc_floating_ip_to_server'
+        r = self.servers_post(
+            server_id, 'action',
+            json_data=dict(addFloatingIp=dict(address=address)))
+        return r.headers
+
+    def disassoc_floating_ip_to_server(self, server_id, address):
+        """Disassociate an address ip from the server with server_id
+
+        :param server_id: (int)
+
+        :param address: (str) the ip address to assign to server (vm)
+
+        :returns: (dict) request headers
+
+        :raises ValueError: if server_id cannot be converted to int
+
+        :raises ValueError: if server_id is not of a int-convertable type
+
+        :raises AssertionError: if address is emtpy
+        """
+        server_id = int(server_id)
+        assert address, 'address is needed for disassoc_floating_ip_to_server'
+        r = self.servers_post(
+            server_id, 'action',
+            json_data=dict(removeFloatingIp=dict(address=address)))
+        return r.headers
index eb469b0..cbe8c41 100644 (file)
@@ -170,22 +170,18 @@ class CycladesRestClient(ComputeClient):
         path = path4url('os-floating-ip-pools')
         return self.get(path, success=success, **kwargs)
 
-    def floating_ips_get(self, success=200, **kwargs):
-        path = path4url('os-floating-ips')
+    def floating_ips_get(self, fip_id='', success=200, **kwargs):
+        path = path4url('os-floating-ips', fip_id)
         return self.get(path, success=success, **kwargs)
 
-    def floating_ips_post(self, json_data, success=201, **kwargs):
-        path = path4url('os-floating-ips')
+    def floating_ips_post(self, json_data, fip_id='', success=201, **kwargs):
+        path = path4url('os-floating-ips', fip_id)
         if json_data is not None:
             json_data = json.dumps(json_data)
             self.set_header('Content-Type', 'application/json')
             self.set_header('Content-Length', len(json_data))
         return self.post(path, data=json_data, success=success, **kwargs)
 
-    def floating_ip_get(self, floating_ip_id, success=200, **kwargs):
-        path = path4url('os-floating-ip', floating_ip_id)
-        return self.get(path, success=success, **kwargs)
-
-    def floating_ip_delete(self, floating_ip_id, success=200, **kwargs):
-        path = path4url('os-floating-ip', floating_ip_id)
+    def floating_ips_delete(self, fip_id, success=200, **kwargs):
+        path = path4url('os-floating-ips', fip_id)
         return self.delete(path, success=success, **kwargs)
index 03582ec..f1b8a97 100644 (file)
@@ -160,7 +160,6 @@ class CycladesRestClient(TestCase):
     @patch('%s.set_header' % rest_pkg)
     @patch('%s.post' % rest_pkg, return_value=FR())
     def test_networks_post(self, post, SH):
-        from json import dumps
         for args in product(
                 ('', 'net_id'),
                 ('', 'cmd'),
@@ -184,7 +183,6 @@ class CycladesRestClient(TestCase):
     @patch('%s.set_header' % rest_pkg)
     @patch('%s.put' % rest_pkg, return_value=FR())
     def test_networks_put(self, put, SH):
-        from json import dumps
         for args in product(
                 ('', 'net_id'),
                 ('', 'cmd'),
@@ -219,56 +217,48 @@ class CycladesRestClient(TestCase):
     @patch('%s.get' % rest_pkg, return_value=FR())
     def test_floating_ips_get(self, get):
         for args in product(
+                ('fip', ''),
                 (200, 204),
                 ({}, {'k': 'v'})):
-            success, kwargs = args
-            r = self.client.floating_ips_get(success, **kwargs)
+            fip, success, kwargs = args
+            r = self.client.floating_ips_get(fip, success, **kwargs)
             self.assertTrue(isinstance(r, FR))
+            expected = '' if not fip else '/%s' % fip
             self.assertEqual(get.mock_calls[-1], call(
-                '/os-floating-ips', success=success, **kwargs))
+                '/os-floating-ips%s' % expected, success=success, **kwargs))
 
     @patch('%s.set_header' % rest_pkg)
     @patch('%s.post' % rest_pkg, return_value=FR())
     def test_floating_ips_post(self, post, SH):
         for args in product(
                 (None, [dict(json="data"), dict(data="json")]),
+                ('fip', ''),
                 (202, 204),
                 ({}, {'k': 'v'})):
-            (json_data, success, kwargs) = args
-            self.client.floating_ips_post(*args[:2], **kwargs)
+            json_data, fip, success, kwargs = args
+            self.client.floating_ips_post(*args[:3], **kwargs)
             if json_data:
                 json_data = dumps(json_data)
                 self.assertEqual(SH.mock_calls[-2:], [
                     call('Content-Type', 'application/json'),
                     call('Content-Length', len(json_data))])
+            expected = '' if not fip else '/%s' % fip
             self.assertEqual(post.mock_calls[-1], call(
-                '/os-floating-ips',
+                '/os-floating-ips%s' % expected,
                 data=json_data, success=success,
                 **kwargs))
 
-    @patch('%s.get' % rest_pkg, return_value=FR())
-    def test_floating_ip_get(self, get):
-        for args in product(
-                ('fip1', 'fip2'),
-                (200, 204),
-                ({}, {'k': 'v'})):
-            fip, success, kwargs = args
-            r = self.client.floating_ip_get(fip, success, **kwargs)
-            self.assertTrue(isinstance(r, FR))
-            self.assertEqual(get.mock_calls[-1], call(
-                '/os-floating-ip/%s' % fip, success=success, **kwargs))
-
     @patch('%s.delete' % rest_pkg, return_value=FR())
-    def test_floating_ip_delete(self, delete):
+    def test_floating_ips_delete(self, delete):
         for args in product(
                 ('fip1', 'fip2'),
                 (200, 204),
                 ({}, {'k': 'v'})):
             fip, success, kwargs = args
-            r = self.client.floating_ip_delete(fip, success, **kwargs)
+            r = self.client.floating_ips_delete(fip, success, **kwargs)
             self.assertTrue(isinstance(r, FR))
             self.assertEqual(delete.mock_calls[-1], call(
-                '/os-floating-ip/%s' % fip, success=success, **kwargs))
+                '/os-floating-ips/%s' % fip, success=success, **kwargs))
 
 
 class CycladesClient(TestCase):
@@ -499,6 +489,93 @@ class CycladesClient(TestCase):
                 self.assertEqual(err.details, [
                     'Network may be still connected to at least one server'])
 
+    @patch('%s.floating_ip_pools_get' % cyclades_pkg, return_value=FR())
+    def test_get_floating_ip_pools(self, get):
+        r = self.client.get_floating_ip_pools()
+        self.assert_dicts_are_equal(r, FR.json)
+        self.assertEqual(get.mock_calls[-1], call())
+
+    @patch('%s.floating_ips_get' % cyclades_pkg, return_value=FR())
+    def test_get_floating_ips(self, get):
+        r = self.client.get_floating_ips()
+        self.assert_dicts_are_equal(r, FR.json)
+        self.assertEqual(get.mock_calls[-1], call())
+
+    @patch('%s.floating_ips_post' % cyclades_pkg, return_value=FR())
+    def test_alloc_floating_ip(self, post):
+        FR.json = dict(floating_ip=dict(
+            fixed_ip='fip',
+            id=1,
+            instance_id='lala',
+            ip='102.0.0.1',
+            pool='pisine'))
+        for args in product(
+                (None, 'pisine'),
+                (None, 'Iwannanip')):
+            r = self.client.alloc_floating_ip(*args)
+            pool, address = args
+            self.assert_dicts_are_equal(r, FR.json['floating_ip'])
+            json_data = dict()
+            if pool:
+                json_data['pool'] = pool
+                if address:
+                    json_data['address'] = address
+            self.assertEqual(post.mock_calls[-1], call(json_data))
+
+    @patch('%s.floating_ips_get' % cyclades_pkg, return_value=FR())
+    def test_get_floating_ip(self, get):
+        FR.json = dict(floating_ip=dict(
+            fixed_ip='fip',
+            id=1,
+            instance_id='lala',
+            ip='102.0.0.1',
+            pool='pisine'))
+        self.assertRaises(AssertionError, self.client.get_floating_ip, None)
+        fip = 'fip'
+        r = self.client.get_floating_ip(fip)
+        self.assert_dicts_are_equal(r, FR.json['floating_ip'])
+        self.assertEqual(get.mock_calls[-1], call(fip))
+
+    @patch('%s.floating_ips_delete' % cyclades_pkg, return_value=FR())
+    def test_delete_floating_ip(self, delete):
+        self.assertRaises(AssertionError, self.client.delete_floating_ip, None)
+        fip = 'fip'
+        r = self.client.delete_floating_ip(fip)
+        self.assert_dicts_are_equal(r, FR.headers)
+        self.assertEqual(delete.mock_calls[-1], call(fip))
+
+    @patch('%s.servers_post' % cyclades_pkg, return_value=FR())
+    def test_assoc_floating_ip_to_server(self, spost):
+        vmid, addr = 42, 'anIpAddress'
+        for err, args in {
+                ValueError: ['not a server id', addr],
+                TypeError: [None, addr],
+                AssertionError: [vmid, None],
+                AssertionError: [vmid, '']}.items():
+            self.assertRaises(
+                err, self.client.assoc_floating_ip_to_server, *args)
+        r = self.client.assoc_floating_ip_to_server(vmid, addr)
+        self.assert_dicts_are_equal(r, FR.headers)
+        expected = dict(addFloatingIp=dict(address=addr))
+        self.assertEqual(
+            spost.mock_calls[-1], call(vmid, 'action', json_data=expected))
+
+    @patch('%s.servers_post' % cyclades_pkg, return_value=FR())
+    def test_disassoc_floating_ip_to_server(self, spost):
+        vmid, addr = 42, 'anIpAddress'
+        for err, args in {
+                ValueError: ['not a server id', addr],
+                TypeError: [None, addr],
+                AssertionError: [vmid, None],
+                AssertionError: [vmid, '']}.items():
+            self.assertRaises(
+                err, self.client.disassoc_floating_ip_to_server, *args)
+        r = self.client.disassoc_floating_ip_to_server(vmid, addr)
+        self.assert_dicts_are_equal(r, FR.headers)
+        expected = dict(removeFloatingIp=dict(address=addr))
+        self.assertEqual(
+            spost.mock_calls[-1], call(vmid, 'action', json_data=expected))
+
 
 if __name__ == '__main__':
     from sys import argv