Merge branch 'feature-compute-v2' into develop
authorStavros Sachtouris <saxtouri@admin.grnet.gr>
Fri, 26 Jul 2013 14:30:49 +0000 (17:30 +0300)
committerStavros Sachtouris <saxtouri@admin.grnet.gr>
Fri, 26 Jul 2013 14:30:49 +0000 (17:30 +0300)
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 7abbc6e..71f62a2 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -37,4 +37,5 @@ Features:
     commission_pending/info/action/accept/reject/resolve/issuejson/issue
 - Implement resize_server and expose it as server resize [#4153]
 - Expose the project API of astakosclient to kamaki CLI [#4155]
+- Full support for compute v2 basic API [#4139]
 
index b35f646..3cc7a86 100644 (file)
@@ -39,27 +39,82 @@ from kamaki.clients.utils import path4url
 class ComputeClient(ComputeRestClient):
     """OpenStack Compute API 1.1 client"""
 
-    def list_servers(self, detail=False):
+    def list_servers(
+            self,
+            detail=False,
+            changes_since=None,
+            image=None,
+            flavor=None,
+            name=None,
+            marker=None,
+            limit=None,
+            status=None,
+            host=None,
+            response_headers=dict(previous=None, next=None)):
         """
         :param detail: if true, append full server details to each item
 
+        :param response_headers: (dict) use it to get previous/next responses
+            Keep the existing dict format to actually get the server responses
+            Use it with very long lists or with marker
+
         :returns: list of server ids and names
         """
-        detail = 'detail' if detail else ''
-        r = self.servers_get(command=detail)
+        r = self.servers_get(
+            detail=bool(detail),
+            changes_since=changes_since,
+            image=image,
+            flavor=flavor,
+            name=name,
+            marker=marker,
+            limit=limit,
+            status=status,
+            host=host)
+        for k, v in response_headers.items():
+            response_headers[k] = r.headers.get(k, v)
         return r.json['servers']
 
-    def get_server_details(self, server_id, **kwargs):
+    def get_server_details(
+            self, server_id,
+            changes_since=None,
+            image=None,
+            flavor=None,
+            name=None,
+            marker=None,
+            limit=None,
+            status=None,
+            host=None,
+            response_headers=dict(previous=None, next=None),
+            **kwargs):
         """Return detailed info for a server
 
         :param server_id: integer (int or str)
 
         :returns: dict with server details
         """
-        r = self.servers_get(server_id, **kwargs)
+        r = self.servers_get(
+            server_id,
+            changes_since=changes_since,
+            image=image,
+            flavor=flavor,
+            name=name,
+            marker=marker,
+            limit=limit,
+            status=status,
+            host=host,
+            **kwargs)
+        for k, v in response_headers.items():
+            response_headers[k] = r.headers.get(k, v)
         return r.json['server']
 
-    def create_server(self, name, flavor_id, image_id, personality=None):
+    def create_server(
+            self, name, flavor_id, image_id,
+            security_group=None,
+            user_data=None,
+            availability_zone=None,
+            metadata=None,
+            personality=None,
+            response_headers=dict(location=None)):
         """Submit request to create a new server
 
         :param name: (str)
@@ -68,6 +123,8 @@ class ComputeClient(ComputeRestClient):
 
         :param image_id: (str) id denoting the OS image to run on the VM
 
+        :param metadata: (dict) vm metadata
+
         :param personality: a list of (file path, file contents) tuples,
             describing files to be injected into VM upon creation.
 
@@ -75,37 +132,22 @@ class ComputeClient(ComputeRestClient):
 
         :raises ClientError: wraps request errors
         """
-        req = {'server': {'name': name,
-                          'flavorRef': flavor_id,
-                          'imageRef': image_id}}
-
-        image = self.get_image_details(image_id)
-        metadata = {}
-        for key in ('os', 'users'):
-            try:
-                metadata[key] = image['metadata'][key]
-            except KeyError:
-                pass
+        req = {'server': {
+            'name': name, 'flavorRef': flavor_id, 'imageRef': image_id}}
+
         if metadata:
             req['server']['metadata'] = metadata
 
         if personality:
             req['server']['personality'] = personality
 
-        try:
-            r = self.servers_post(json_data=req)
-        except ClientError as err:
-            try:
-                if isinstance(err.details, list):
-                    tmp_err = err.details
-                else:
-                    errd = '%s' % err.details
-                    tmp_err = errd.split(',')
-                tmp_err = tmp_err[0].split(':')
-                tmp_err = tmp_err[2].split('"')
-                err.message = tmp_err[1]
-            finally:
-                raise err
+        r = self.servers_post(
+            json_data=req,
+            security_group=security_group,
+            user_data=user_data,
+            availability_zone=availability_zone)
+        for k, v in response_headers.items():
+            response_headers[k] = r.headers.get(k, v)
         return r.json['server']
 
     def update_server_name(self, server_id, new_name):
@@ -132,15 +174,33 @@ class ComputeClient(ComputeRestClient):
         r = self.servers_delete(server_id)
         return r.headers
 
+    def change_admin_password(self, server_id, new_password):
+        """
+        :param server_id: (int)
+
+        :param new_password: (str)
+        """
+        req = {"changePassword": {"adminPass": new_password}}
+        r = self.servers_action_post(server_id, json_data=req)
+        return r.headers
+
+    def rebuild_server(self, server_id, response_headers=dict(location=None)):
+        """OS"""
+        server = self.get_server_details(server_id)
+        r = self.servers_action_post(
+            server_id, json_data=dict(rebuild=server['server']))
+        for k, v in response_headers.items():
+            response_headers[k] = r.headers.get(k, v)
+        return r.json['server']
+
     def reboot_server(self, server_id, hard=False):
         """
         :param server_id: integer (str or int)
 
         :param hard: perform a hard reboot if true, soft reboot otherwise
         """
-        boot_type = 'HARD' if hard else 'SOFT'
-        req = {'reboot': {'type': boot_type}}
-        r = self.servers_post(server_id, 'action', json_data=req)
+        req = {'reboot': {'type': 'HARD' if hard else 'SOFT'}}
+        r = self.servers_action_post(server_id, json_data=req)
         return r.headers
 
     def resize_server(self, server_id, flavor_id):
@@ -152,10 +212,41 @@ class ComputeClient(ComputeRestClient):
         :returns: (dict) request headers
         """
         req = {'resize': {'flavorRef': flavor_id}}
-        r = self.servers_post(server_id, 'action', json_data=req)
+        r = self.servers_action_post(server_id, json_data=req)
+        return r.headers
+
+    def confirm_resize_server(self, server_id):
+        """OS"""
+        r = self.servers_action_post(
+            server_id, json_data=dict(confirmResize=None))
         return r.headers
 
-    def get_server_metadata(self, server_id, key=''):
+    def revert_resize_server(self, server_id):
+        """OS"""
+        r = self.servers_action_post(
+            server_id, json_data=dict(revertResize=None))
+        return r.headers
+
+    def create_server_image(self, server_id, image_name, **metadata):
+        """OpenStack method for taking snapshots"""
+        req = dict(createImage=dict(name=image_name, metadata=metadata))
+        r = self.servers_action_post(server_id, json_data=req)
+        return r.headers['location']
+
+    def start_server(self, server_id):
+        """OS Extentions"""
+        req = {'os-start': None}
+        r = self.servers_action_post(server_id, json_data=req, success=202)
+        return r.headers
+
+    def shutdown_server(self, server_id):
+        """OS Extentions"""
+        req = {'os-stop': None}
+        r = self.servers_action_post(server_id, json_data=req, success=202)
+        return r.headers
+
+    def get_server_metadata(self, server_id, key='', response_headers=dict(
+            previous=None, next=None)):
         """
         :param server_id: integer (str or int)
 
@@ -163,8 +254,9 @@ class ComputeClient(ComputeRestClient):
 
         :returns: a key:val dict of requests metadata
         """
-        command = path4url('metadata', key)
-        r = self.servers_get(server_id, command)
+        r = self.servers_metadata_get(server_id, key)
+        for k, v in response_headers.items():
+            response_headers[k] = r.headers.get(k, v)
         return r.json['meta' if key else 'metadata']
 
     def create_server_metadata(self, server_id, key, val):
@@ -178,11 +270,13 @@ class ComputeClient(ComputeRestClient):
         :returns: dict of updated key:val metadata
         """
         req = {'meta': {key: val}}
-        r = self.servers_put(
-            server_id, 'metadata/' + key, json_data=req, success=201)
+        r = self.servers_metadata_put(
+            server_id, key, json_data=req, success=201)
         return r.json['meta']
 
-    def update_server_metadata(self, server_id, **metadata):
+    def update_server_metadata(
+            self, server_id,
+            response_headers=dict(previous=None, next=None), **metadata):
         """
         :param server_id: integer (str or int)
 
@@ -191,8 +285,9 @@ class ComputeClient(ComputeRestClient):
         :returns: dict of updated key:val metadata
         """
         req = {'metadata': metadata}
-        r = self.servers_post(
-            server_id, 'metadata', json_data=req, success=201)
+        r = self.servers_metadata_post(server_id, json_data=req, success=201)
+        for k, v in response_headers.items():
+            response_headers[k] = r.headers.get(k, v)
         return r.json['metadata']
 
     def delete_server_metadata(self, server_id, key):
@@ -203,16 +298,19 @@ class ComputeClient(ComputeRestClient):
 
         :returns: (dict) response headers
         """
-        r = self.servers_delete(server_id, 'metadata/' + key)
+        r = self.servers_metadata_delete(server_id, key)
         return r.headers
 
-    def list_flavors(self, detail=False):
+    def list_flavors(self, detail=False, response_headers=dict(
+            previous=None, next=None)):
         """
         :param detail: (bool) detailed flavor info if set, short if not
 
         :returns: (list) flavor info
         """
-        r = self.flavors_get(command='detail' if detail else '')
+        r = self.flavors_get(detail=bool(detail))
+        for k, v in response_headers.items():
+            response_headers[k] = r.headers.get(k, v)
         return r.json['flavors']
 
     def get_flavor_details(self, flavor_id):
@@ -224,14 +322,16 @@ class ComputeClient(ComputeRestClient):
         r = self.flavors_get(flavor_id)
         return r.json['flavor']
 
-    def list_images(self, detail=False):
+    def list_images(self, detail=False, response_headers=dict(
+            next=None, previous=None)):
         """
         :param detail: (bool) detailed info if set, short if not
 
         :returns: dict id,name + full info if detail
         """
-        detail = 'detail' if detail else ''
-        r = self.images_get(command=detail)
+        r = self.images_get(detail=bool(detail))
+        for k, v in response_headers.items():
+            response_headers[k] = r.headers.get(k, v)
         return r.json['images']
 
     def get_image_details(self, image_id, **kwargs):
@@ -256,7 +356,8 @@ class ComputeClient(ComputeRestClient):
         r = self.images_delete(image_id)
         return r.headers
 
-    def get_image_metadata(self, image_id, key=''):
+    def get_image_metadata(self, image_id, key='', response_headers=dict(
+            previous=None, next=None)):
         """
         :param image_id: (str)
 
@@ -264,8 +365,9 @@ class ComputeClient(ComputeRestClient):
 
         :returns (dict) metadata if key not set, specific metadatum otherwise
         """
-        command = path4url('metadata', key)
-        r = self.images_get(image_id, command)
+        r = self.images_metadata_get(image_id, key)
+        for k, v in response_headers.items():
+            response_headers[k] = r.headers.get(k, v)
         return r.json['meta' if key else 'metadata']
 
     def create_image_metadata(self, image_id, key, val):
@@ -279,10 +381,12 @@ class ComputeClient(ComputeRestClient):
         :returns: (dict) updated metadata
         """
         req = {'meta': {key: val}}
-        r = self.images_put(image_id, 'metadata/' + key, json_data=req)
+        r = self.images_metadata_put(image_id, key, json_data=req)
         return r.json['meta']
 
-    def update_image_metadata(self, image_id, **metadata):
+    def update_image_metadata(
+            self, image_id,
+            response_headers=dict(previous=None, next=None), **metadata):
         """
         :param image_id: (str)
 
@@ -291,7 +395,9 @@ class ComputeClient(ComputeRestClient):
         :returns: updated metadata
         """
         req = {'metadata': metadata}
-        r = self.images_post(image_id, 'metadata', json_data=req)
+        r = self.images_metadata_post(image_id, json_data=req)
+        for k, v in response_headers.items():
+            response_headers[k] = r.headers.get(k, v)
         return r.json['metadata']
 
     def delete_image_metadata(self, image_id, key):
@@ -302,8 +408,7 @@ class ComputeClient(ComputeRestClient):
 
         :returns: (dict) response headers
         """
-        command = path4url('metadata', key)
-        r = self.images_delete(image_id, command)
+        r = self.images_metadata_delete(image_id, key)
         return r.headers
 
     def get_floating_ip_pools(self, tenant_id):
index 31ec1ec..4db5e13 100644 (file)
@@ -38,12 +38,9 @@ import json
 
 class ComputeRestClient(Client):
 
-    def servers_get(self, server_id='', command='', success=200, **kwargs):
-        """GET base_url/servers[/server_id][/command] request
-
-        :param server_id: integer (as int or str)
-
-        :param command: 'ips', 'stats', or ''
+    # NON-cyclades
+    def limits_get(self, success=200, **kwargs):
+        """GET base_url/limits
 
         :param success: success code or list or tupple of accepted success
             codes. if server response code is not in this list, a ClientError
@@ -51,210 +48,363 @@ class ComputeRestClient(Client):
 
         :returns: request response
         """
-        path = path4url('servers', server_id, command)
+        path = path4url('limits')
         return self.get(path, success=success, **kwargs)
 
-    def servers_delete(self, server_id='', command='', success=204, **kwargs):
-        """DEL ETE base_url/servers[/server_id][/command] request
+    def servers_get(
+            self,
+            server_id='', detail=False,
+            changes_since=None,
+            image=None,
+            flavor=None,
+            name=None,
+            marker=None,
+            limit=None,
+            status=None,
+            host=None,
+            success=200,
+            **kwargs):
+        """GET base_url/servers/['detail'|<server_id>]
 
-        :param server_id: integer (as int or str)
+        :param server_id: (int or int str) ignored if detail
 
-        :param command: 'ips', 'stats', or ''
+        :param detail: (boolean)
 
-        :param success: success code or list or tupple of accepted success
-            codes. if server response code is not in this list, a ClientError
-            raises
+        --- Parameters ---
+
+        :param changes-since: A time/date stamp for when the server last
+            changed status
+
+        :param image: Name of the image in URL format
+
+        :param flavor: Name of the flavor in URL format
+
+        :param name: Name of the server as a string
+
+        :param marker: UUID of the server at which you want to set a marker
+
+        :param limit: (int) limit of values to return
+
+        :param status: Status of the server (e.g. filter on "ACTIVE")
+
+        :param host: Name of the host as a string
 
         :returns: request response
         """
-        path = path4url('servers', server_id, command)
-        return self.delete(path, success=success, **kwargs)
+        if not server_id:
+            self.set_param('changes-since', changes_since, iff=changes_since)
+            self.set_param('image', image, iff=image)
+            self.set_param('flavor', flavor, iff=flavor)
+            self.set_param('name', name, iff=name)
+            self.set_param('marker', marker, iff=marker)
+            self.set_param('limit', limit, iff=limit)
+            self.set_param('status', status, iff=status)
+            self.set_param('host', host, iff=host)
+
+        path = path4url('servers', 'detail' if detail else (server_id or ''))
+        return self.get(path, success=success, **kwargs)
 
     def servers_post(
             self,
-            server_id='',
-            command='',
+            security_group=None,
+            user_data=None,
+            availability_zone=None,
             json_data=None,
             success=202,
             **kwargs):
-        """POST base_url/servers[/server_id]/[command] request
+        """POST base_url/servers
 
-        :param server_id: integer (as int or str)
+        :param json_data: a json-formated dict that will be send as data
 
-        :param command: 'ips', 'stats', or ''
+        --- Parameters
 
-        :param json_data: a json-formated dict that will be send as data
+        :param security_group: (str)
 
-        :param success: success code or list or tupple of accepted success
-            codes. if server response code is not in this list, a ClientError
-            raises
+        :param user_data: Use to pass configuration information or scripts upon
+            launch. Must be Base64 encoded.
+
+        :param availability_zone: (str)
 
         :returns: request response
         """
-        data = json_data
+
+        self.set_param('security_group', security_group, iff=security_group)
+        self.set_param('user_data', user_data, iff=user_data)
+        self.set_param(
+            'availability_zone', availability_zone, iff=availability_zone)
+
         if json_data:
-            data = json.dumps(json_data)
+            json_data = json.dumps(json_data)
             self.set_header('Content-Type', 'application/json')
-            self.set_header('Content-Length', len(data))
+            self.set_header('Content-Length', len(json_data))
 
-        path = path4url('servers', server_id, command)
-        return self.post(path, data=data, success=success, **kwargs)
+        path = path4url('servers')
+        return self.post(path, data=json_data, success=success, **kwargs)
 
     def servers_put(
             self,
-            server_id='',
-            command='',
-            json_data=None,
-            success=204,
+            server_id, server_name=None, json_data=None, success=204,
             **kwargs):
-        """PUT base_url/servers[/server_id]/[command] request
-
-        :param server_id: integer (as int or str)
-
-        :param command: 'ips', 'stats', or ''
+        """PUT base_url/servers/<server_id>
 
         :param json_data: a json-formated dict that will be send as data
 
-        :param success: success code or list or tupple of accepted success
-            codes. if server response code is not in this list, a ClientError
-            raises
+        :param success: success code (iterable of) codes
+
+        :raises ClientError: if returned code not in success list
 
         :returns: request response
         """
-        data = json_data
-        if json_data is not None:
-            data = json.dumps(json_data)
+        self.set_param('server', server_name, iff=server_name)
+
+        if json_data:
+            json_data = json.dumps(json_data)
             self.set_header('Content-Type', 'application/json')
-            self.set_header('Content-Length', len(data))
+            self.set_header('Content-Length', len(json_data))
+        path = path4url('servers', server_id)
+        return self.put(path, data=json_data, success=success, **kwargs)
 
-        path = path4url('servers', server_id, command)
-        return self.put(path, data=data, success=success, **kwargs)
+    def servers_delete(self, server_id, success=204, **kwargs):
+        """DEL ETE base_url/servers/<server_id>
 
-    def flavors_get(self, flavor_id='', command='', success=200, **kwargs):
-        """GET base_url[/flavor_id][/command]
+        :param json_data: a json-formated dict that will be send as data
 
-        :param flavor_id: integer (str or int)
+        :param success: success code (iterable of) codes
 
-        :param command: flavor service command
+        :raises ClientError: if returned code not in success list
 
-        :param success: success code or list or tupple of accepted success
-            codes. if server response code is not in this list, a ClientError
-            raises
+        :returns: request response
+        """
+        path = path4url('servers', server_id)
+        return self.delete(path, success=success, **kwargs)
+
+    def servers_metadata_get(self, server_id, key=None, success=200, **kwargs):
+        """GET base_url/servers/<server_id>/metadata[/key]
 
         :returns: request response
         """
-        path = path4url('flavors', flavor_id, command)
+        path = path4url('servers', server_id, 'metadata', key or '')
         return self.get(path, success=success, **kwargs)
 
-    def images_get(self, image_id='', command='', success=200, **kwargs):
-        """GET base_url[/image_id][/command]
+    def servers_metadata_post(
+            self, server_id, json_data=None, success=202, **kwargs):
+        """POST base_url/servers/<server_id>/metadata
+
+        :returns: request response
+        """
+        if json_data:
+            json_data = json.dumps(json_data)
+            self.set_header('Content-Type', 'application/json')
+            self.set_header('Content-Length', len(json_data))
+        path = path4url('servers', server_id, 'metadata')
+        return self.post(path, data=json_data, success=success, **kwargs)
 
-        :param image_id: string
+    def servers_metadata_put(
+            self, server_id, key=None, json_data=None, success=204, **kwargs):
+        """PUT base_url/servers/<server_id>/metadata[/key]
 
-        :param command: image server command
+        :returns: request response
+        """
+        if json_data:
+            json_data = json.dumps(json_data)
+            self.set_header('Content-Type', 'application/json')
+            self.set_header('Content-Length', len(json_data))
+        path = path4url('servers', server_id, 'metadata', key or '')
+        return self.put(path, data=json_data, success=success, **kwargs)
 
-        :param success: success code or list or tupple of accepted success
-            codes. if server response code is not in this list, a ClientError
-            raises
+    def servers_metadata_delete(self, server_id, key, success=204, **kwargs):
+        """DEL ETE base_url/servers/<server_id>/metadata[/key]
 
         :returns: request response
         """
-        path = path4url('images', image_id, command)
-        return self.get(path, success=success, **kwargs)
+        path = path4url('servers', server_id, 'metadata', key)
+        return self.delete(path, success=success, **kwargs)
 
-    def images_delete(self, image_id='', command='', success=204, **kwargs):
-        """DELETE base_url[/image_id][/command]
+    def servers_action_post(
+            self, server_id, json_data=None, success=202, **kwargs):
+        """POST base_url/servers/<server_id>/action
 
-        :param image_id: string
+        :returns: request response
+        """
+        if json_data:
+            json_data = json.dumps(json_data)
+            self.set_header('Content-Type', 'application/json')
+            self.set_header('Content-Length', len(json_data))
+        path = path4url('servers', server_id, 'action')
+        return self.post(path, data=json_data, success=success, **kwargs)
 
-        :param command: image server command
+    def servers_ips_get(
+            self, server_id,
+            network_id=None, changes_since=None, success=(304, 200),
+            **kwargs):
+        """GET base_url/servers/<server_id>/ips[/network_id]
 
-        :param success: success code or list or tuple of accepted success
-            codes. if server response code is not in this list, a ClientError
-            raises
+        :param changes_since: time/date stamp in UNIX/epoch time. Checks for
+            changes since a previous request.
 
         :returns: request response
         """
-        path = path4url('images', image_id, command)
-        return self.delete(path, success=success, **kwargs)
+        self.set_param('changes-since', changes_since, iff=changes_since)
+        path = path4url('servers', server_id, 'ips', network_id or '')
+        return self.get(path, success=success, **kwargs)
 
-    def images_post(
+    def images_get(
             self,
             image_id='',
-            command='',
-            json_data=None,
-            success=201,
+            detail=False,
+            changes_since=None,
+            server_name=None,
+            name=None,
+            status=None,
+            marker=None,
+            limit=None,
+            type=None,
+            success=200,
             **kwargs):
-        """POST base_url/images[/image_id]/[command] request
+        """GET base_url[/image_id][/command]
 
-        :param image_id: string
+        :param image_id: (str) ignored if detail
 
-        :param command: image server command
+        :param detail: (bool)
 
-        :param json_data: (dict) will be send as data
+        --- Parameters ---
+        :param changes_since: when the image last changed status
 
-        :param success: success code or list or tuple of accepted success
-            codes. if server response code is not in this list, a ClientError
-            raises
+        :param server_name: Name of the server in URL format
+
+        :param name: Name of the image
+
+        :param status: Status of the image (e.g. filter on "ACTIVE")
+
+        :param marker: UUID of the image at which you want to set a marker
+
+        :param limit: Integer value for the limit of values to return
+
+        :param type: Type of image (e.g. BASE, SERVER, or ALL)
+
+        :returns: request response
+        """
+        if not image_id:
+            self.set_param('changes-since', changes_since, iff=changes_since)
+            self.set_param('name', name, iff=name)
+            self.set_param('status', status, iff=status)
+            self.set_param('marker', marker, iff=marker)
+            self.set_param('limit', limit, iff=limit)
+            self.set_param('type', type, iff=type)
+            self.set_param('server', server_name, iff=server_name)
+
+        path = path4url('images', 'detail' if detail else (image_id or ''))
+        return self.get(path, success=success, **kwargs)
+
+    def images_delete(self, image_id='', success=204, **kwargs):
+        """DEL ETE base_url/images/<image_id>
+
+        :returns: request response
+        """
+        path = path4url('images', image_id)
+        return self.delete(path, success=success, **kwargs)
+
+    def images_metadata_get(self, image_id, key=None, success=200, **kwargs):
+        """GET base_url/<image_id>/metadata[/key]
+
+        :returns: request response
+        """
+        path = path4url('images', image_id, 'metadata', key or '')
+        return self.get(path, success=success, **kwargs)
+
+    def images_metadata_post(
+            self, image_id, json_data=None, success=201, **kwargs):
+        """POST base_url/images/<image_id>/metadata
 
         :returns: request response
         """
-        data = json_data
         if json_data is not None:
-            data = json.dumps(json_data)
+            json_data = json.dumps(json_data)
             self.set_header('Content-Type', 'application/json')
-            self.set_header('Content-Length', len(data))
+            self.set_header('Content-Length', len(json_data))
 
-        path = path4url('images', image_id, command)
-        return self.post(path, data=data, success=success, **kwargs)
+        path = path4url('images', image_id, 'metadata')
+        return self.post(path, data=json_data, success=success, **kwargs)
+
+    def images_metadata_put(
+            self, image_id, key=None, json_data=None, success=201, **kwargs):
+        """PUT base_url/images/<image_id>/metadata
 
-    def images_put(
+        :returns: request response
+        """
+        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))
+
+        path = path4url('images', image_id, 'metadata')
+        return self.put(path, data=json_data, success=success, **kwargs)
+
+    def images_metadata_delete(self, image_id, key, success=204, **kwargs):
+        """DEL ETE base_url/images/<image_id>/metadata/key
+
+        :returns: request response
+        """
+        path = path4url('images', image_id, 'metadata', key)
+        return self.delete(path, success=success, **kwargs)
+
+    def flavors_get(
             self,
-            image_id='',
-            command='',
-            json_data=None,
-            success=201,
+            flavor_id='',
+            detail=False,
+            changes_since=None,
+            minDisk=None,
+            minRam=None,
+            marker=None,
+            limit=None,
+            success=200,
             **kwargs):
-        """PUT base_url/images[/image_id]/[command] request
+        """GET base_url[/flavor_id][/command]
 
-        :param image_id: string
+        :param flavor_id: ignored if detail
 
-        :param command: image server command
+        :param detail: (bool)
 
-        :param json_data: (dict) will be send as data
+        --- Parameters ---
 
-        :param success: success code or list or tuple of accepted success
-            codes. if server response code is not in this list, a ClientError
-            raises
+        :param changes_since: when the flavor last changed
+
+        :param minDisk: minimum disk space in GB filter
+
+        :param minRam: minimum RAM filter
+
+        :param marker: UUID of the flavor at which to set a marker
+
+        :param limit: limit the number of returned values
 
         :returns: request response
         """
-        data = json_data
-        if json_data is not None:
-            data = json.dumps(json_data)
-            self.set_header('Content-Type', 'application/json')
-            self.set_header('Content-Length', len(data))
-
-        path = path4url('images', image_id, command)
-        return self.put(path, data=data, success=success, **kwargs)
+        if not flavor_id:
+            self.set_param('changes-since', changes_since, iff=changes_since)
+            self.set_param('minDisk', minDisk, iff=minDisk)
+            self.set_param('minRam', minRam, iff=minRam)
+            self.set_param('marker', marker, iff=marker)
+            self.set_param('limit', limit, iff=limit)
+
+        path = path4url('flavors', 'detail' if detail else (flavor_id or ''))
+        return self.get(path, success=success, **kwargs)
 
-    def floating_ip_pools_get(self, tenant_id, success=200, **kwargs):
-        path = path4url(tenant_id, 'os-floating-ip-pools')
+    def floating_ip_pools_get(self, success=200, **kwargs):
+        path = path4url('os-floating-ip-pools')
         return self.get(path, success=success, **kwargs)
 
-    def floating_ips_get(self, tenant_id, ip='', success=200, **kwargs):
-        path = path4url(tenant_id, 'os-floating-ips', ip or '')
+    def floating_ips_get(self, ip='', success=200, **kwargs):
+        path = path4url('os-floating-ips', ip or '')
         return self.get(path, success=success, **kwargs)
 
-    def floating_ips_post(
-            self, tenant_id, json_data, ip='', success=201, **kwargs):
-        path = path4url(tenant_id, 'os-floating-ips', ip or '')
+    def floating_ips_post(self, json_data, ip='', success=201, **kwargs):
+        path = path4url('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_ips_delete(self, tenant_id, ip='', success=204, **kwargs):
-        path = path4url(tenant_id, 'os-floating-ips', ip or '')
+    def floating_ips_delete(self, ip='', success=204, **kwargs):
+        path = path4url('os-floating-ips', ip or '')
         return self.delete(path, success=success, **kwargs)
index f05df80..874b0f0 100644 (file)
@@ -35,6 +35,8 @@ from mock import patch, call
 from unittest import TestCase
 from itertools import product
 from json import dumps
+from sys import stdout
+import json
 
 from kamaki.clients import ClientError, compute
 
@@ -107,6 +109,18 @@ class FR(object):
     status_code = 200
 
 
+def print_iterations(old, new):
+    if new:
+        if new % 1000:
+            return old
+        stdout.write('\b' * len('%s' % old))
+        stdout.write('%s' % new)
+    else:
+        stdout.write('# of loops:  ')
+    stdout.flush()
+    return new
+
+
 class ComputeRestClient(TestCase):
 
     """Set up a ComputesRest thorough test"""
@@ -118,154 +132,307 @@ class ComputeRestClient(TestCase):
     def tearDown(self):
         FR.json = vm_recv
 
+    @patch('%s.set_param' % rest_pkg)
     @patch('%s.get' % rest_pkg, return_value=FR())
-    def _test_get(self, service, get):
-        for args in product(
+    def _test_get(self, service, params, get, set_param):
+        method = getattr(self.client, '%s_get' % service)
+        param_args = [({}, {k: k}, {k: v[1]}) for k, v in params.items()]
+        num_of_its = 0
+        for i, args in enumerate(product(
                 ('', '%s_id' % service),
-                ('', 'cmd'),
+                (None, False, True),
                 (200, 204),
-                ({}, {'k': 'v'})):
-            (srv_id, command, success, kwargs) = args
-            method = getattr(self.client, '%s_get' % service)
-            method(*args[:3], **kwargs)
-            srv_str = '/%s' % srv_id if srv_id else ''
-            cmd_str = '/%s' % command if command else ''
-            self.assertEqual(get.mock_calls[-1], call(
-                '/%s%s%s' % (service, srv_str, cmd_str),
-                success=success,
-                **kwargs))
-
-    def test_servers_get(self):
-        self._test_get('servers')
-
-    def test_flavors_get(self):
-        self._test_get('flavors')
-
-    def test_images_get(self):
-        self._test_get('images')
-
-    @patch('%s.delete' % rest_pkg, return_value=FR())
-    def _test_delete(self, service, delete):
-        for args in product(
-                ('', '%s_id' % service),
-                ('', 'cmd'),
-                (204, 208),
-                ({}, {'k': 'v'})):
-            (srv_id, command, success, kwargs) = args
-            method = getattr(self.client, '%s_delete' % service)
-            method(*args[:3], **kwargs)
-            vm_str = '/%s' % srv_id if srv_id else ''
-            cmd_str = '/%s' % command if command else ''
-            self.assertEqual(delete.mock_calls[-1], call(
-                '/%s%s%s' % (service, vm_str, cmd_str),
-                success=success,
-                **kwargs))
-
-    def test_servers_delete(self):
-        self._test_delete('servers')
-
-    def test_images_delete(self):
-        self._test_delete('images')
+                ({}, {'k': 'v'}),
+                *param_args)):
+            (srv_id, detail, success, kwargs) = args[:4]
+            kwargs['success'] = success
+            srv_kwargs = dict()
+            for param in args[4:]:
+                srv_kwargs.update(param)
+            srv_kwargs.update(kwargs)
+            method(*args[:2], **srv_kwargs)
+            srv_str = '/detail' if detail else (
+                '/%s' % srv_id) if srv_id else ''
+            self.assertEqual(
+                get.mock_calls[-1],
+                call('/%s%s' % (service, srv_str), **kwargs))
+            param_calls = []
+            for k, v in params.items():
+                real_v = srv_kwargs.get(k, v[1]) if not srv_id else v[1]
+                param_calls.append(call(v[0], real_v, iff=real_v))
+            actual = set_param.mock_calls[- len(param_calls):]
+            self.assertEqual(sorted(actual), sorted(param_calls))
+
+            num_of_its = print_iterations(num_of_its, i)
+        print ('\b' * len('%s' % num_of_its)) + ('%s' % i)
+
+    @patch('%s.set_param' % rest_pkg)
+    @patch('%s.get' % rest_pkg, return_value=FR())
+    def _test_srv_cmd_get(self, srv, cmd, params, get, set_param):
+        method = getattr(self.client, '%s_%s_get' % (srv, cmd))
+        param_args = [({}, {k: k}, {k: v[1]}) for k, v in params.items()]
+        num_of_its = 0
+        for i, args in enumerate(product(
+                ('some_server_id', 'other_server_id'),
+                (None, 'xtra_id'),
+                ((304, 200), (1000)),
+                ({}, {'k': 'v'}),
+                *param_args)):
+            srv_id, xtra_id, success, kwargs = args[:4]
+            kwargs = dict(kwargs)
+            kwargs['success'] = success
+            srv_kwargs = dict()
+            for param in args[4:]:
+                srv_kwargs.update(param)
+            srv_kwargs.update(kwargs)
+            method(*args[:2], **srv_kwargs)
+            srv_str = '/%s/%s/%s' % (srv, srv_id, cmd)
+            srv_str += ('/%s' % xtra_id) if xtra_id else ''
+            self.assertEqual(get.mock_calls[-1], call(srv_str, **kwargs))
+            param_calls = []
+            for k, v in params.items():
+                real_v = srv_kwargs.get(k, v[1])
+                param_calls.append(call(v[0], real_v, iff=real_v))
+            actual = set_param.mock_calls[- len(param_calls):]
+            self.assertEqual(sorted(actual), sorted(param_calls))
+
+            num_of_its = print_iterations(num_of_its, i)
+        print ('\b' * len('%s' % num_of_its)) + ('%s' % i)
 
     @patch('%s.set_header' % rest_pkg)
+    @patch('%s.set_param' % rest_pkg)
     @patch('%s.post' % rest_pkg, return_value=FR())
-    def _test_post(self, service, post, SH):
-        for args in product(
-                ('', '%s_id' % service),
-                ('', 'cmd'),
-                (None, [dict(json="data"), dict(data="json")]),
-                (202, 204),
-                ({}, {'k': 'v'})):
-            (srv_id, command, json_data, success, kwargs) = args
-            method = getattr(self.client, '%s_post' % service)
-            method(*args[:4], **kwargs)
-            vm_str = '/%s' % srv_id if srv_id else ''
-            cmd_str = '/%s' % command if command else ''
+    def _test_post(self, srv, cmd, params, post, set_param, set_header):
+        method = getattr(
+            self.client, '%s_%spost' % (srv, ('%s_' % cmd) if cmd else ''))
+        param_args = [({}, {k: k}, {k: v[1]}) for k, v in params.items()]
+        num_of_its = 0
+        for i, args in enumerate(product(
+                ('%s_id' % srv, 'some_value'),
+                (
+                    None,
+                    {'some': {'data': 'in json'}},
+                    ['k1', {'k2': 'v2', 'k3': 'v3'}, 'k4']),
+                (202, 1453),
+                ({}, {'k': 'v'}),
+                *param_args)):
+            srv_id, json_data, success, kwargs = args[:4]
+            kwargs = dict(kwargs)
+            cmd_args = (srv_id, ) if cmd else ()
+            kwargs['success'] = success
+            srv_kwargs = dict()
+            for param in args[4:]:
+                srv_kwargs.update(param)
+            srv_kwargs.update(kwargs)
+            srv_kwargs['json_data'] = json_data
+            method(*cmd_args, **srv_kwargs)
+            srv_str = '/%s%s' % (
+                srv, (('/%s/%s' % (srv_id, cmd)) if cmd else ''))
+            kwargs['data'] = json.dumps(json_data) if json_data else None
+            self.assertEqual(post.mock_calls[-1], call(srv_str, **kwargs))
+
+            param_calls = []
+            for k, v in params.items():
+                real_v = srv_kwargs.get(k, v[1])
+                param_calls.append(call(v[0], real_v, iff=real_v))
+            actual = set_param.mock_calls[- len(param_calls):]
+            self.assertEqual(sorted(actual), sorted(param_calls))
+
             if json_data:
-                json_data = dumps(json_data)
-                self.assertEqual(SH.mock_calls[-2:], [
+                self.assertEqual(set_header.mock_calls[-2:], [
                     call('Content-Type', 'application/json'),
-                    call('Content-Length', len(json_data))])
-            self.assertEqual(post.mock_calls[-1], call(
-                '/%s%s%s' % (service, vm_str, cmd_str),
-                data=json_data, success=success,
-                **kwargs))
+                    call('Content-Length', len(kwargs['data']))])
 
-    def test_servers_post(self):
-        self._test_post('servers')
-
-    def test_images_post(self):
-        self._test_post('images')
+            num_of_its = print_iterations(num_of_its, i)
+        print ('\b' * len('%s' % num_of_its)) + ('%s' % i)
 
     @patch('%s.set_header' % rest_pkg)
+    @patch('%s.set_param' % rest_pkg)
     @patch('%s.put' % rest_pkg, return_value=FR())
-    def _test_put(self, service, put, SH):
-        for args in product(
-                ('', '%s_id' % service),
-                ('', 'cmd'),
+    def _test_put(self, srv, cmd, params, put, set_param, set_headers):
+        method = getattr(self.client, '%s_%sput' % (
+            srv, ('%s_' % cmd) if cmd else ''))
+        param_args = [({}, {k: k}, {k: v[1]}) for k, v in params.items()]
+        num_of_its = 0
+        for i, args in enumerate(product(
+                ('some_value', '%s_id' % srv),
                 (None, [dict(json="data"), dict(data="json")]),
                 (204, 504),
-                ({}, {'k': 'v'})):
-            (server_id, command, json_data, success, kwargs) = args
-            method = getattr(self.client, '%s_put' % service)
-            method(*args[:4], **kwargs)
-            vm_str = '/%s' % server_id if server_id else ''
-            cmd_str = '/%s' % command if command else ''
+                ({}, {'k': 'v'}),
+                *param_args)):
+            srv_id, json_data, success, kwargs = args[:4]
+            kwargs = dict(kwargs)
+            kwargs['success'] = success
+            srv_kwargs = dict()
+            for param in args[4:]:
+                srv_kwargs.update(param)
+            srv_kwargs.update(kwargs)
+            srv_kwargs['json_data'] = json_data
+            method(srv_id, **srv_kwargs)
+            srv_str = '/%s/%s%s' % (srv, srv_id, ('/%s' % cmd if cmd else ''))
+
             if json_data:
                 json_data = dumps(json_data)
-                self.assertEqual(SH.mock_calls[-2:], [
+                self.assertEqual(set_headers.mock_calls[-2:], [
                     call('Content-Type', 'application/json'),
                     call('Content-Length', len(json_data))])
-            self.assertEqual(put.mock_calls[-1], call(
-                '/%s%s%s' % (service, vm_str, cmd_str),
-                data=json_data, success=success,
-                **kwargs))
+            self.assertEqual(
+                put.mock_calls[-1],
+                call(srv_str, data=json_data, **kwargs))
+
+            param_calls = []
+            for k, v in params.items():
+                real_v = srv_kwargs.get(k, v[1])
+                param_calls.append(call(v[0], real_v, iff=real_v))
+            actual = set_param.mock_calls[- len(param_calls):]
+            self.assertEqual(sorted(actual), sorted(param_calls))
+
+            num_of_its = print_iterations(num_of_its, i)
+        print ('\b' * len('%s' % num_of_its)) + ('%s' % i)
+
+    @patch('%s.delete' % rest_pkg, return_value=FR())
+    def _test_delete(self, srv, cmd, delete):
+        method = getattr(
+            self.client, '%s_%sdelete' % (srv, ('%s_' % cmd) if cmd else ''))
+        cmd_params = ('some_cmd_value', 'some_other_value') if cmd else ()
+        num_of_its = 0
+        for i, args in enumerate(product(
+                ('%s_id' % srv, 'some_value'),
+                (204, 208),
+                ({}, {'k': 'v'}),
+                *cmd_params)):
+            (srv_id, success, kwargs) = args[:3]
+            kwargs = dict(kwargs)
+            kwargs['success'] = success
+            cmd_value = args[-1] if cmd else ''
+            method_args = (srv_id, cmd_value) if cmd else (srv_id, )
+            method(*method_args, **kwargs)
+            srv_str = '/%s/%s' % (srv, srv_id)
+            cmd_str = ('/%s/%s' % (cmd, cmd_value)) if cmd else ''
+            self.assertEqual(
+                delete.mock_calls[-1],
+                call('%s%s' % (srv_str, cmd_str), **kwargs))
+            num_of_its = print_iterations(num_of_its, i)
+        print ('\b' * len('%s' % num_of_its)) + ('%s' % i)
+
+    @patch('%s.get' % rest_pkg, return_value=FR())
+    def test_limits_get(self, get):
+        self.client.limits_get(success='some_val')
+        get.assert_called_once_with('/limits', success='some_val')
+
+    def test_servers_get(self):
+        params = dict(
+            changes_since=('changes-since', None),
+            image=('image', None),
+            flavor=('flavor', None),
+            name=('name', None),
+            marker=('marker', None),
+            limit=('limit', None),
+            status=('status', None),
+            host=('host', None))
+        self._test_get('servers', params)
+
+    def test_servers_post(self):
+        params = dict(
+            security_group=('security_group', None),
+            user_data=('user_data', None),
+            availability_zone=('availability_zone', None))
+        self._test_post('servers', None, params)
 
     def test_servers_put(self):
-        self._test_put('servers')
+        self._test_put('servers', None, dict(server_name=('server', None)))
+
+    def test_servers_delete(self):
+        self._test_delete('servers', None)
+
+    def test_servers_metadata_get(self):
+        self._test_srv_cmd_get('servers', 'metadata', {})
+
+    def test_servers_metadata_post(self):
+        self._test_post('servers', 'metadata', {})
+
+    def test_servers_metadata_put(self):
+        self._test_put('servers', 'metadata', {})
+
+    def test_images_metadata_delete(self):
+        self._test_delete('images', 'metadata')
 
-    def test_images_put(self):
-        self._test_put('images')
+    def test_servers_action_post(self):
+        self._test_post('servers', 'action', {})
+
+    def test_servers_ips_get(self):
+        params = dict(changes_since=('changes-since', None))
+        self._test_srv_cmd_get('servers', 'ips', params)
+
+    def test_images_get(self):
+        param = dict(
+            changes_since=('changes-since', None),
+            server_name=('server', None),
+            name=('name', None),
+            status=('status', None),
+            marker=('marker', None),
+            limit=('limit', None),
+            type=('type', None))
+        self._test_get('images', param)
+
+    def test_images_delete(self):
+        self._test_delete('images', None)
+
+    def test_images_metadata_get(self):
+        self._test_srv_cmd_get('images', 'metadata', {})
+
+    def test_images_metadata_post(self):
+        self._test_post('images', 'metadata', {})
+
+    def test_images_metadata_put(self):
+        self._test_put('images', 'metadata', {})
+
+    def test_servers_metadata_delete(self):
+        self._test_delete('servers', 'metadata')
+
+    def test_flavors_get(self):
+        params = dict(
+            changes_since=('changes-since', None),
+            minDisk=('minDisk', None),
+            minRam=('minRam', None),
+            marker=('marker', None),
+            limit=('limit', None))
+        self._test_get('flavors', params)
 
     @patch('%s.get' % rest_pkg, return_value=FR())
     def test_floating_ip_pools_get(self, get):
         for args in product(
-                ('tenant1', 'tenant2'),
                 (200, 204),
                 ({}, {'k': 'v'})):
-            tenant_id, success, kwargs = args
-            r = self.client.floating_ip_pools_get(tenant_id, success, **kwargs)
+            success, kwargs = args
+            r = self.client.floating_ip_pools_get(success, **kwargs)
             self.assertTrue(isinstance(r, FR))
             self.assertEqual(get.mock_calls[-1], call(
-                '/%s/os-floating-ip-pools' % tenant_id,
-                success=success, **kwargs))
+                '/os-floating-ip-pools', success=success, **kwargs))
 
     @patch('%s.get' % rest_pkg, return_value=FR())
     def test_floating_ips_get(self, get):
         for args in product(
-                ('tenant1', 'tenant2'),
                 ('', '192.193.194.195'),
                 (200, 204),
                 ({}, {'k': 'v'})):
-            tenant_id, ip, success, kwargs = args
-            r = self.client.floating_ips_get(*args[:3], **kwargs)
+            ip, success, kwargs = args
+            r = self.client.floating_ips_get(*args[:2], **kwargs)
             self.assertTrue(isinstance(r, FR))
             expected = '' if not ip else '/%s' % ip
             self.assertEqual(get.mock_calls[-1], call(
-                '/%s/os-floating-ips%s' % (tenant_id, expected),
-                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(
-                ('tenant1', 'tenant2'),
                 (None, [dict(json="data"), dict(data="json")]),
                 ('', '192.193.194.195'),
                 (202, 204),
                 ({}, {'k': 'v'})):
-            (tenant_id, json_data, ip, success, kwargs) = args
-            self.client.floating_ips_post(*args[:4], **kwargs)
+            json_data, ip, 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:], [
@@ -273,24 +440,22 @@ class ComputeRestClient(TestCase):
                     call('Content-Length', len(json_data))])
             expected = '' if not ip else '/%s' % ip
             self.assertEqual(post.mock_calls[-1], call(
-                '/%s/os-floating-ips%s' % (tenant_id, expected),
+                '/os-floating-ips%s' % expected,
                 data=json_data, success=success,
                 **kwargs))
 
     @patch('%s.delete' % rest_pkg, return_value=FR())
     def test_floating_ips_delete(self, delete):
         for args in product(
-                ('tenant1', 'tenant2'),
                 ('', '192.193.194.195'),
                 (204,),
                 ({}, {'k': 'v'})):
-            tenant_id, ip, success, kwargs = args
-            r = self.client.floating_ips_delete(*args[:3], **kwargs)
+            ip, success, kwargs = args
+            r = self.client.floating_ips_delete(*args[:2], **kwargs)
             self.assertTrue(isinstance(r, FR))
             expected = '' if not ip else '/%s' % ip
             self.assertEqual(delete.mock_calls[-1], call(
-                '/%s/os-floating-ips%s' % (tenant_id, expected),
-                success=success, **kwargs))
+                '/os-floating-ips%s' % expected, success=success, **kwargs))
 
 
 class ComputeClient(TestCase):
@@ -313,10 +478,7 @@ class ComputeClient(TestCase):
         FR.status_code = 200
         FR.json = vm_recv
 
-    @patch(
-        '%s.get_image_details' % compute_pkg,
-        return_value=img_recv['image'])
-    def test_create_server(self, GID):
+    def test_create_server(self):
         with patch.object(
                 compute.ComputeClient, 'servers_post',
                 side_effect=ClientError(
@@ -327,26 +489,70 @@ class ComputeClient(TestCase):
                 self.client.create_server,
                 vm_name, fid, img_ref)
 
-        with patch.object(
-                compute.ComputeClient, 'servers_post',
-                return_value=FR()) as post:
-            r = self.client.create_server(vm_name, fid, img_ref)
-            self.assertEqual(r, FR.json['server'])
-            self.assertEqual(GID.mock_calls[-1], call(img_ref))
-            self.assertEqual(post.mock_calls[-1], call(json_data=vm_send))
-            prsn = 'Personality string (does not work with real servers)'
-            self.client.create_server(vm_name, fid, img_ref, prsn)
-            expected = dict(server=dict(vm_send['server']))
-            expected['server']['personality'] = prsn
-            self.assertEqual(post.mock_calls[-1], call(json_data=expected))
+        for params in product(
+                ('security_group', None),
+                ('user_data', None),
+                ('availability_zone', None),
+                (None, {'os': 'debian', 'users': 'root'})):
+            kwargs = dict()
+            for i, k in enumerate((
+                    'security_group', 'user_data', 'availability_zone')):
+                if params[i]:
+                    kwargs[k] = params[i]
+            with patch.object(
+                    compute.ComputeClient, 'servers_post',
+                    return_value=FR()) as post:
+                r = self.client.create_server(vm_name, fid, img_ref, **kwargs)
+                self.assertEqual(r, FR.json['server'])
+                exp_json = dict(server=dict(
+                    flavorRef=fid, name=vm_name, imageRef=img_ref))
+                for k in set([
+                        'security_group',
+                        'user_data',
+                        'availability_zone']).difference(kwargs):
+                    kwargs[k] = None
+                self.assertEqual(
+                    post.mock_calls[-1], call(json_data=exp_json, **kwargs))
+                prsn = 'Personality string (does not work with real servers)'
+                self.client.create_server(
+                    vm_name, fid, img_ref, personality=prsn, **kwargs)
+                exp_json['server']['personality'] = prsn
+                self.assertEqual(
+                    post.mock_calls[-1], call(json_data=exp_json, **kwargs))
+                kwargs.pop('personality', None)
+                exp_json['server'].pop('personality', None)
+                mtdt = 'Metadata dict here'
+                self.client.create_server(
+                    vm_name, fid, img_ref, metadata=mtdt, **kwargs)
+                exp_json['server']['metadata'] = mtdt
+                self.assertEqual(
+                    post.mock_calls[-1], call(json_data=exp_json, **kwargs))
 
     @patch('%s.servers_get' % compute_pkg, return_value=FR())
     def test_list_servers(self, SG):
         FR.json = vm_list
-        for detail in (False, True):
-            r = self.client.list_servers(detail)
-            self.assertEqual(SG.mock_calls[-1], call(
-                command='detail' if detail else ''))
+        for args in product(
+                (False, True),
+                ({}, dict(status='status')),
+                ({}, dict(name='name')),
+                ({}, dict(image='image')),
+                ({}, dict(flavor='flavor')),
+                ({}, dict(host='host')),
+                ({}, dict(limit='limit')),
+                ({}, dict(marker='marker')),
+                ({}, dict(changes_since='changes_since'))):
+            detail = args[0]
+            kwargs = dict()
+            for param in args[1:]:
+                kwargs.update(param)
+            r = self.client.list_servers(detail, **kwargs)
+            for k in set([
+                    'status', 'name',
+                    'image', 'flavor',
+                    'host', 'limit',
+                    'marker', 'changes_since']).difference(kwargs):
+                kwargs[k] = None
+            self.assertEqual(SG.mock_calls[-1], call(detail=detail, **kwargs))
             for i, vm in enumerate(vm_list['servers']):
                 self.assert_dicts_are_equal(r[i], vm)
             self.assertEqual(i + 1, len(r))
@@ -354,9 +560,27 @@ class ComputeClient(TestCase):
     @patch('%s.servers_get' % compute_pkg, return_value=FR())
     def test_get_server_details(self, SG):
         vm_id = vm_recv['server']['id']
-        r = self.client.get_server_details(vm_id)
-        SG.assert_called_once_with(vm_id)
-        self.assert_dicts_are_equal(r, vm_recv['server'])
+        for args in product(
+                ({}, dict(status='status')),
+                ({}, dict(name='name')),
+                ({}, dict(image='image')),
+                ({}, dict(flavor='flavor')),
+                ({}, dict(host='host')),
+                ({}, dict(limit='limit')),
+                ({}, dict(marker='marker')),
+                ({}, dict(changes_since='changes_since'))):
+            kwargs = dict()
+            for param in args:
+                kwargs.update(param)
+            r = self.client.get_server_details(vm_id, **kwargs)
+            for k in set([
+                    'status', 'name',
+                    'image', 'flavor',
+                    'host', 'limit',
+                    'marker', 'changes_since']).difference(kwargs):
+                kwargs[k] = None
+            self.assertEqual(SG.mock_calls[-1], call(vm_id, **kwargs))
+            self.assert_dicts_are_equal(r, vm_recv['server'])
 
     @patch('%s.servers_put' % compute_pkg, return_value=FR())
     def test_update_server_name(self, SP):
@@ -366,23 +590,22 @@ class ComputeClient(TestCase):
         SP.assert_called_once_with(vm_id, json_data=dict(
             server=dict(name=new_name)))
 
-    @patch('%s.servers_post' % compute_pkg, return_value=FR())
+    @patch('%s.servers_action_post' % compute_pkg, return_value=FR())
     def test_reboot_server(self, SP):
         vm_id = vm_recv['server']['id']
         for hard in (None, True):
             self.client.reboot_server(vm_id, hard=hard)
-            self.assertEqual(SP.mock_calls[-1], call(
-                vm_id, 'action',
-                json_data=dict(reboot=dict(type='HARD' if hard else 'SOFT'))))
+            self.assertEqual(SP.mock_calls[-1], call(vm_id, json_data=dict(
+                reboot=dict(type='HARD' if hard else 'SOFT'))))
 
-    @patch('%s.servers_post' % compute_pkg, return_value=FR())
+    @patch('%s.servers_action_post' % compute_pkg, return_value=FR())
     def test_resize_server(self, SP):
         vm_id, flavor = vm_recv['server']['id'], flavor_list['flavors'][1]
         self.client.resize_server(vm_id, flavor['id'])
         exp = dict(resize=dict(flavorRef=flavor['id']))
-        SP.assert_called_once_with(vm_id, 'action', json_data=exp)
+        SP.assert_called_once_with(vm_id, json_data=exp)
 
-    @patch('%s.servers_put' % compute_pkg, return_value=FR())
+    @patch('%s.servers_metadata_put' % compute_pkg, return_value=FR())
     def test_create_server_metadata(self, SP):
         vm_id = vm_recv['server']['id']
         metadata = dict(m1='v1', m2='v2', m3='v3')
@@ -391,17 +614,17 @@ class ComputeClient(TestCase):
             r = self.client.create_server_metadata(vm_id, k, v)
             self.assert_dicts_are_equal(r, vm_recv['server'])
             self.assertEqual(SP.mock_calls[-1], call(
-                vm_id, 'metadata/%s' % k,
+                vm_id, '%s' % k,
                 json_data=dict(meta={k: v}), success=201))
 
-    @patch('%s.servers_get' % compute_pkg, return_value=FR())
+    @patch('%s.servers_metadata_get' % compute_pkg, return_value=FR())
     def test_get_server_metadata(self, SG):
         vm_id = vm_recv['server']['id']
         metadata = dict(m1='v1', m2='v2', m3='v3')
         FR.json = dict(metadata=metadata)
         r = self.client.get_server_metadata(vm_id)
         FR.json = dict(meta=metadata)
-        SG.assert_called_once_with(vm_id, '/metadata')
+        SG.assert_called_once_with(vm_id, '')
         self.assert_dicts_are_equal(r, metadata)
 
         for k, v in metadata.items():
@@ -409,9 +632,9 @@ class ComputeClient(TestCase):
             r = self.client.get_server_metadata(vm_id, k)
             self.assert_dicts_are_equal(r, {k: v})
             self.assertEqual(
-                SG.mock_calls[-1], call(vm_id, '/metadata/%s' % k))
+                SG.mock_calls[-1], call(vm_id, '%s' % k))
 
-    @patch('%s.servers_post' % compute_pkg, return_value=FR())
+    @patch('%s.servers_metadata_post' % compute_pkg, return_value=FR())
     def test_update_server_metadata(self, SP):
         vm_id = vm_recv['server']['id']
         metadata = dict(m1='v1', m2='v2', m3='v3')
@@ -419,22 +642,21 @@ class ComputeClient(TestCase):
         r = self.client.update_server_metadata(vm_id, **metadata)
         self.assert_dicts_are_equal(r, metadata)
         SP.assert_called_once_with(
-            vm_id, 'metadata',
-            json_data=dict(metadata=metadata), success=201)
+            vm_id, json_data=dict(metadata=metadata), success=201)
 
-    @patch('%s.servers_delete' % compute_pkg, return_value=FR())
+    @patch('%s.servers_metadata_delete' % compute_pkg, return_value=FR())
     def test_delete_server_metadata(self, SD):
         vm_id = vm_recv['server']['id']
         key = 'metakey'
         self.client.delete_server_metadata(vm_id, key)
-        SD.assert_called_once_with(vm_id, 'metadata/' + key)
+        SD.assert_called_once_with(vm_id, key)
 
     @patch('%s.flavors_get' % compute_pkg, return_value=FR())
     def test_list_flavors(self, FG):
         FR.json = flavor_list
-        for cmd in ('', 'detail'):
-            r = self.client.list_flavors(detail=(cmd == 'detail'))
-            self.assertEqual(FG.mock_calls[-1], call(command=cmd))
+        for detail in ('', 'detail'):
+            r = self.client.list_flavors(detail=bool(detail))
+            self.assertEqual(FG.mock_calls[-1], call(detail=bool(detail)))
             self.assertEqual(r, flavor_list['flavors'])
 
     @patch('%s.flavors_get' % compute_pkg, return_value=FR())
@@ -447,9 +669,9 @@ class ComputeClient(TestCase):
     @patch('%s.images_get' % compute_pkg, return_value=FR())
     def test_list_images(self, IG):
         FR.json = img_list
-        for cmd in ('', 'detail'):
-            r = self.client.list_images(detail=(cmd == 'detail'))
-            self.assertEqual(IG.mock_calls[-1], call(command=cmd))
+        for detail in ('', 'detail'):
+            r = self.client.list_images(detail=detail)
+            self.assertEqual(IG.mock_calls[-1], call(detail=bool(detail)))
             expected = img_list['images']
             for i in range(len(r)):
                 self.assert_dicts_are_equal(expected[i], r[i])
@@ -461,15 +683,13 @@ class ComputeClient(TestCase):
         IG.assert_called_once_with(img_ref)
         self.assert_dicts_are_equal(r, img_recv['image'])
 
-    @patch('%s.images_get' % compute_pkg, return_value=FR())
+    @patch('%s.images_metadata_get' % compute_pkg, return_value=FR())
     def test_get_image_metadata(self, IG):
         for key in ('', '50m3k3y'):
             FR.json = dict(meta=img_recv['image']) if (
                 key) else dict(metadata=img_recv['image'])
             r = self.client.get_image_metadata(img_ref, key)
-            self.assertEqual(IG.mock_calls[-1], call(
-                '%s' % img_ref,
-                '/metadata%s' % (('/%s' % key) if key else '')))
+            self.assertEqual(IG.mock_calls[-1], call(img_ref, key or ''))
             self.assert_dicts_are_equal(img_recv['image'], r)
 
     @patch('%s.servers_delete' % compute_pkg, return_value=FR())
@@ -483,31 +703,30 @@ class ComputeClient(TestCase):
         self.client.delete_image(img_ref)
         ID.assert_called_once_with(img_ref)
 
-    @patch('%s.images_put' % compute_pkg, return_value=FR())
+    @patch('%s.images_metadata_put' % compute_pkg, return_value=FR())
     def test_create_image_metadata(self, IP):
         (key, val) = ('k1', 'v1')
         FR.json = dict(meta=img_recv['image'])
         r = self.client.create_image_metadata(img_ref, key, val)
         IP.assert_called_once_with(
-            img_ref, 'metadata/%s' % key,
+            img_ref, '%s' % key,
             json_data=dict(meta={key: val}))
         self.assert_dicts_are_equal(r, img_recv['image'])
 
-    @patch('%s.images_post' % compute_pkg, return_value=FR())
+    @patch('%s.images_metadata_post' % compute_pkg, return_value=FR())
     def test_update_image_metadata(self, IP):
         metadata = dict(m1='v1', m2='v2', m3='v3')
         FR.json = dict(metadata=metadata)
         r = self.client.update_image_metadata(img_ref, **metadata)
         IP.assert_called_once_with(
-            img_ref, 'metadata',
-            json_data=dict(metadata=metadata))
+            img_ref, json_data=dict(metadata=metadata))
         self.assert_dicts_are_equal(r, metadata)
 
-    @patch('%s.images_delete' % compute_pkg, return_value=FR())
+    @patch('%s.images_metadata_delete' % compute_pkg, return_value=FR())
     def test_delete_image_metadata(self, ID):
         key = 'metakey'
         self.client.delete_image_metadata(img_ref, key)
-        ID.assert_called_once_with(img_ref, '/metadata/%s' % key)
+        ID.assert_called_once_with(img_ref, '%s' % key)
 
     @patch('%s.floating_ip_pools_get' % compute_pkg, return_value=FR())
     def test_get_floating_ip_pools(self, get):
index 6981e4d..ecac9be 100644 (file)
@@ -41,6 +41,38 @@ from kamaki.clients import ClientError
 class CycladesClient(CycladesRestClient):
     """Synnefo Cyclades Compute API client"""
 
+    def create_server(
+            self, name, flavor_id, image_id,
+            metadata=None, personality=None):
+        """Submit request to create a new server
+
+        :param name: (str)
+
+        :param flavor_id: integer id denoting a preset hardware configuration
+
+        :param image_id: (str) id denoting the OS image to run on the VM
+
+        :param metadata: (dict) vm metadata updated by os/users image metadata
+
+        :param personality: a list of (file path, file contents) tuples,
+            describing files to be injected into VM upon creation.
+
+        :returns: a dict with the new VMs details
+
+        :raises ClientError: wraps request errors
+        """
+        image = self.get_image_details(image_id)
+        metadata = metadata or dict()
+        for key in ('os', 'users'):
+            try:
+                metadata[key] = image['metadata'][key]
+            except KeyError:
+                pass
+
+        return super(CycladesClient, self).create_server(
+            name, flavor_id, image_id,
+            metadata=metadata, personality=personality)
+
     def start_server(self, server_id):
         """Submit a startup request
 
@@ -49,7 +81,7 @@ class CycladesClient(CycladesRestClient):
         :returns: (dict) response headers
         """
         req = {'start': {}}
-        r = self.servers_post(server_id, 'action', json_data=req, success=202)
+        r = self.servers_action_post(server_id, json_data=req, success=202)
         return r.headers
 
     def shutdown_server(self, server_id):
@@ -60,7 +92,7 @@ class CycladesClient(CycladesRestClient):
         :returns: (dict) response headers
         """
         req = {'shutdown': {}}
-        r = self.servers_post(server_id, 'action', json_data=req, success=202)
+        r = self.servers_action_post(server_id, json_data=req, success=202)
         return r.headers
 
     def get_server_console(self, server_id):
@@ -70,7 +102,7 @@ class CycladesClient(CycladesRestClient):
         :returns: (dict) info to set a VNC connection to VM
         """
         req = {'console': {'type': 'vnc'}}
-        r = self.servers_post(server_id, 'action', json_data=req, success=200)
+        r = self.servers_action_post(server_id, json_data=req, success=200)
         return r.json['console']
 
     def get_firewall_profile(self, server_id):
@@ -99,30 +131,17 @@ class CycladesClient(CycladesRestClient):
         :returns: (dict) response headers
         """
         req = {'firewallProfile': {'profile': profile}}
-        r = self.servers_post(server_id, 'action', json_data=req, success=202)
+        r = self.servers_action_post(server_id, json_data=req, success=202)
         return r.headers
 
-    def list_servers(self, detail=False, changes_since=None):
-        """
-        :param detail: (bool) append full server details to each item if true
-
-        :param changes_since: (date)
-
-        :returns: list of server ids and names
-        """
-        detail = 'detail' if detail else ''
-        r = self.servers_get(command=detail, changes_since=changes_since)
-        return r.json['servers']
-
     def list_server_nics(self, server_id):
         """
         :param server_id: integer (str or int)
 
         :returns: (dict) network interface connections
         """
-        r = self.servers_get(server_id, 'ips')
+        r = self.servers_ips_get(server_id)
         return r.json['attachments']
-        #return r.json['addresses']
 
     def get_server_stats(self, server_id):
         """
@@ -130,7 +149,7 @@ class CycladesClient(CycladesRestClient):
 
         :returns: (dict) auto-generated graphs of statistics (urls)
         """
-        r = self.servers_get(server_id, 'stats')
+        r = self.servers_stats_get(server_id)
         return r.json['stats']
 
     def list_networks(self, detail=False):
@@ -432,9 +451,8 @@ class CycladesClient(CycladesRestClient):
         """
         server_id = int(server_id)
         assert address, 'address is needed for attach_floating_ip'
-        r = self.servers_post(
-            server_id, 'action',
-            json_data=dict(addFloatingIp=dict(address=address)))
+        req = dict(addFloatingIp=dict(address=address))
+        r = self.servers_action_post(server_id, json_data=req)
         return r.headers
 
     def detach_floating_ip(self, server_id, address):
@@ -454,7 +472,6 @@ class CycladesClient(CycladesRestClient):
         """
         server_id = int(server_id)
         assert address, 'address is needed for detach_floating_ip'
-        r = self.servers_post(
-            server_id, 'action',
-            json_data=dict(removeFloatingIp=dict(address=address)))
+        req = dict(removeFloatingIp=dict(address=address))
+        r = self.servers_action_post(server_id, json_data=req)
         return r.headers
index 7ebdc6a..cfb1580 100644 (file)
@@ -39,29 +39,9 @@ import json
 class CycladesRestClient(ComputeClient):
     """Synnefo Cyclades REST API Client"""
 
-    def servers_get(
-            self,
-            server_id='',
-            command='',
-            success=200,
-            changes_since=None,
-            **kwargs):
-        """GET base_url/servers[/server_id][/command] request
-
-        :param server_id: integer (as int or str)
-
-        :param command: 'ips', 'stats', or ''
-
-        :param success: success code or list or tupple of accepted success
-            codes. if server response code is not in this list, a ClientError
-            raises
-
-        :param changes_since: (date)
-
-        :returns: request response
-        """
-        path = path4url('servers', server_id, command)
-        self.set_param('changes-since', changes_since, changes_since)
+    def servers_stats_get(self, server_id, success=200, **kwargs):
+        """GET base_url/servers/<server_id>/stats"""
+        path = path4url('servers', server_id, 'stats')
         return self.get(path, success=success, **kwargs)
 
     def networks_get(
@@ -165,23 +145,3 @@ class CycladesRestClient(ComputeClient):
 
         path = path4url('networks', network_id, command)
         return self.put(path, data=data, success=success, **kwargs)
-
-    def floating_ip_pools_get(self, success=200, **kwargs):
-        path = path4url('os-floating-ip-pools')
-        return self.get(path, success=success, **kwargs)
-
-    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, 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_ips_delete(self, fip_id, success=200, **kwargs):
-        path = path4url('os-floating-ips', fip_id)
-        return self.delete(path, success=success, **kwargs)
index b1b84c5..0073ec3 100644 (file)
@@ -103,28 +103,6 @@ class CycladesRestClient(TestCase):
     def tearDown(self):
         FR.json = vm_recv
 
-    @patch('%s.set_param' % rest_pkg)
-    @patch('%s.get' % rest_pkg, return_value=FR())
-    def test_servers_get(self, get, SP):
-        for args in product(
-                ('', 'vm_id'),
-                ('', 'cmd'),
-                (200, 204),
-                (None, '50m3-d473'),
-                ({}, {'k': 'v'})):
-            (srv_id, command, success, changes_since, kwargs) = args
-            self.client.servers_get(*args[:4], **kwargs)
-            srv_str = '/%s' % srv_id if srv_id else ''
-            cmd_str = '/%s' % command if command else ''
-            self.assertEqual(get.mock_calls[-1], call(
-                '/servers%s%s' % (srv_str, cmd_str),
-                success=success,
-                **kwargs))
-            if changes_since:
-                self.assertEqual(
-                    SP.mock_calls[-1],
-                    call('changes-since', changes_since, changes_since))
-
     @patch('%s.get' % rest_pkg, return_value=FR())
     def test_networks_get(self, get):
         for args in product(
@@ -281,44 +259,28 @@ class CycladesClient(TestCase):
         FR.status_code = 200
         FR.json = vm_recv
 
-    @patch('%s.servers_get' % cyclades_pkg, return_value=FR())
-    def test_list_servers(self, SG):
-        FR.json = vm_list
-        for detail, since in ((0, 0), (True, 0), (0, 'd473'), (True, 'd473')):
-            r = self.client.list_servers(detail=detail, changes_since=since)
-            self.assertEqual(SG.mock_calls[-1], call(
-                command='detail' if detail else '',
-                changes_since=since))
-            expected = vm_list['servers']
-            for i, vm in enumerate(r):
-                self.assert_dicts_are_equal(vm, expected[i])
-            self.assertEqual(i + 1, len(expected))
-
-    @patch('%s.servers_post' % cyclades_pkg, return_value=FR())
+    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
     def test_shutdown_server(self, SP):
         vm_id = vm_recv['server']['id']
         self.client.shutdown_server(vm_id)
         SP.assert_called_once_with(
-            vm_id, 'action',
-            json_data=dict(shutdown=dict()), success=202)
+            vm_id, json_data=dict(shutdown=dict()), success=202)
 
-    @patch('%s.servers_post' % cyclades_pkg, return_value=FR())
+    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
     def test_start_server(self, SP):
         vm_id = vm_recv['server']['id']
         self.client.start_server(vm_id)
         SP.assert_called_once_with(
-            vm_id, 'action',
-            json_data=dict(start=dict()), success=202)
+            vm_id, json_data=dict(start=dict()), success=202)
 
-    @patch('%s.servers_post' % cyclades_pkg, return_value=FR())
+    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
     def test_get_server_console(self, SP):
         cnsl = dict(console=dict(info1='i1', info2='i2', info3='i3'))
         FR.json = cnsl
         vm_id = vm_recv['server']['id']
         r = self.client.get_server_console(vm_id)
         SP.assert_called_once_with(
-            vm_id, 'action',
-            json_data=dict(console=dict(type='vnc')), success=200)
+            vm_id, json_data=dict(console=dict(type='vnc')), success=200)
         self.assert_dicts_are_equal(r, cnsl['console'])
 
     def test_get_firewall_profile(self):
@@ -338,23 +300,13 @@ class CycladesClient(TestCase):
                 self.client.get_firewall_profile,
                 vm_id)
 
-    @patch('%s.servers_post' % cyclades_pkg, return_value=FR())
+    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
     def test_set_firewall_profile(self, SP):
         vm_id = vm_recv['server']['id']
         v = firewalls['attachments'][0]['firewallProfile']
         self.client.set_firewall_profile(vm_id, v)
-        SP.assert_called_once_with(
-            vm_id, 'action',
-            json_data=dict(firewallProfile=dict(profile=v)), success=202)
-
-    @patch('%s.servers_get' % cyclades_pkg, return_value=FR())
-    def test_get_server_stats(self, SG):
-        vm_id = vm_recv['server']['id']
-        stats = dict(stat1='v1', stat2='v2', stat3='v3', stat4='v4')
-        FR.json = dict(stats=stats)
-        r = self.client.get_server_stats(vm_id)
-        SG.assert_called_once_with(vm_id, 'stats')
-        self.assert_dicts_are_equal(stats, r)
+        SP.assert_called_once_with(vm_id, json_data=dict(
+            firewallProfile=dict(profile=v)), success=202)
 
     @patch('%s.networks_post' % cyclades_pkg, return_value=FR())
     def test_create_network(self, NP):
@@ -407,13 +359,13 @@ class CycladesClient(TestCase):
                 json_data=dict(remove=dict(attachment=nic_id)))
             self.assertEqual(r, 1)
 
-    @patch('%s.servers_get' % cyclades_pkg, return_value=FR())
+    @patch('%s.servers_ips_get' % cyclades_pkg, return_value=FR())
     def test_list_server_nics(self, SG):
         vm_id = vm_recv['server']['id']
         nics = dict(attachments=[dict(id='nic1'), dict(id='nic2')])
         FR.json = nics
         r = self.client.list_server_nics(vm_id)
-        SG.assert_called_once_with(vm_id, 'ips')
+        SG.assert_called_once_with(vm_id)
         expected = nics['attachments']
         for i in range(len(r)):
             self.assert_dicts_are_equal(r[i], expected[i])
@@ -544,7 +496,7 @@ class CycladesClient(TestCase):
         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())
+    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
     def test_attach_floating_ip(self, spost):
         vmid, addr = 42, 'anIpAddress'
         for err, args in {
@@ -557,10 +509,9 @@ class CycladesClient(TestCase):
         r = self.client.attach_floating_ip(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))
+        self.assertEqual(spost.mock_calls[-1], call(vmid, json_data=expected))
 
-    @patch('%s.servers_post' % cyclades_pkg, return_value=FR())
+    @patch('%s.servers_action_post' % cyclades_pkg, return_value=FR())
     def test_detach_floating_ip(self, spost):
         vmid, addr = 42, 'anIpAddress'
         for err, args in {
@@ -573,8 +524,7 @@ class CycladesClient(TestCase):
         r = self.client.detach_floating_ip(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))
+        self.assertEqual(spost.mock_calls[-1], call(vmid, json_data=expected))
 
 
 if __name__ == '__main__':