commit b6afe2ec4e0a9ce7061608dc8cdd06183fa20e01
Author: Giorgos Korfiatis <gkorf@grnet.gr>
Date:   Mon Sep 16 15:03:42 2013 +0300

    Adapt cyclades commands for new projects
    
    Add optional argument --project in create subcommands;
    add subcommand reassign for servers, networks and ips.

diff --git a/kamaki/cli/commands/cyclades.py b/kamaki/cli/commands/cyclades.py
index 6756419..32c8c15 100644
--- a/kamaki/cli/commands/cyclades.py
+++ b/kamaki/cli/commands/cyclades.py
@@ -474,18 +474,20 @@ class server_create(_init_cyclades, _optional_json, _server_wait):
             'If neither --network or --no-network are used, the default '
             'network policy is applied. These policies are set on the cloud, '
             'so kamaki is oblivious to them',
-            '--no-network')
+            '--no-network'),
+        project=ValueArgument('Assign the server to project', '--project'),
     )
     required = ('server_name', 'flavor_id', 'image_id')
 
     @errors.cyclades.cluster_size
-    def _create_cluster(self, prefix, flavor_id, image_id, size):
+    def _create_cluster(self, prefix, flavor_id, image_id, size, project=None):
         networks = self['network_configuration'] or (
             [] if self['no_network'] else None)
         servers = [dict(
             name='%s%s' % (prefix, i if size > 1 else ''),
             flavor_id=flavor_id,
             image_id=image_id,
+            project=project,
             personality=self['personality'],
             networks=networks) for i in range(1, 1 + size)]
         if size == 1:
@@ -518,7 +520,8 @@ class server_create(_init_cyclades, _optional_json, _server_wait):
     @errors.cyclades.flavor_id
     def _run(self, name, flavor_id, image_id):
         for r in self._create_cluster(
-                name, flavor_id, image_id, size=self['cluster_size'] or 1):
+                name, flavor_id, image_id, size=self['cluster_size'] or 1,
+                project=self['project']):
             if not r:
                 self.error('Create %s: server response was %s' % (name, r))
                 continue
@@ -657,6 +660,22 @@ class server_modify(_init_cyclades, _optional_output_cmd):
 
 
 @command(server_cmds)
+class server_reassign(_init_cyclades, _optional_json):
+    """Assign a VM to a different project
+    """
+
+    @errors.generic.all
+    @errors.cyclades.connection
+    @errors.cyclades.server_id
+    def _run(self, server_id, project):
+        self.client.reassign_server(server_id, project)
+
+    def main(self, server_id, project):
+        super(self.__class__, self)._run()
+        self._run(server_id=server_id, project=project)
+
+
+@command(server_cmds)
 class server_delete(_init_cyclades, _optional_output_cmd, _server_wait):
     """Delete a virtual server"""
 
diff --git a/kamaki/cli/commands/network.py b/kamaki/cli/commands/network.py
index 1580f24..1bc0886 100644
--- a/kamaki/cli/commands/network.py
+++ b/kamaki/cli/commands/network.py
@@ -189,6 +189,7 @@ class network_create(_init_network, _optional_json):
         name=ValueArgument('Network name', '--name'),
         shared=FlagArgument(
             'Make network shared (special privileges required)', '--shared'),
+        project=ValueArgument('Assign the network to project', '--project'),
         network_type=NetworkTypeArgument(
             'Valid network types: %s' % (', '.join(NetworkTypeArgument.types)),
             '--type')
@@ -199,7 +200,9 @@ class network_create(_init_network, _optional_json):
     @errors.cyclades.network_type
     def _run(self, network_type):
         net = self.client.create_network(
-            network_type, name=self['name'], shared=self['shared'])
+            network_type, name=self['name'],
+            shared=self['shared'],
+            project=self['project'])
         self._print(net, self.print_dict)
 
     def main(self):
@@ -208,6 +211,22 @@ class network_create(_init_network, _optional_json):
 
 
 @command(network_cmds)
+class network_reassign(_init_network, _optional_json):
+    """Assign a network to a different project
+    """
+
+    @errors.generic.all
+    @errors.cyclades.connection
+    @errors.cyclades.network_id
+    def _run(self, network_id, project):
+        self.client.reassign_network(network_id, project)
+
+    def main(self, network_id, project):
+        super(self.__class__, self)._run()
+        self._run(network_id=network_id, project=project)
+
+
+@command(network_cmds)
 class network_delete(_init_network, _optional_output_cmd):
     """Delete a network"""
 
@@ -607,7 +626,8 @@ class ip_create(_init_network, _optional_json):
     arguments = dict(
         network_id=ValueArgument(
             'The network to preserve the IP on', '--network-id'),
-        ip_address=ValueArgument('Allocate an IP address', '--address')
+        ip_address=ValueArgument('Allocate an IP address', '--address'),
+        project=ValueArgument('Assign the IP to project', '--project'),
     )
     required = ('network_id', )
 
@@ -617,7 +637,8 @@ class ip_create(_init_network, _optional_json):
     def _run(self, network_id):
         self._print(
             self.client.create_floatingip(
-                network_id, floating_ip_address=self['ip_address']),
+                network_id, floating_ip_address=self['ip_address'],
+                project=self['project']),
             self.print_dict)
 
     def main(self):
@@ -626,6 +647,20 @@ class ip_create(_init_network, _optional_json):
 
 
 @command(ip_cmds)
+class ip_reassign(_init_network, _optional_output_cmd):
+    """Assign a floating IP to a different project
+    """
+    @errors.generic.all
+    @errors.cyclades.connection
+    def _run(self, ip, project):
+        self._optional_output(self.client.reassign_floating_ip(ip, project))
+
+    def main(self, IP, project):
+        super(self.__class__, self)._run()
+        self._run(ip=IP, project=project)
+
+
+@command(ip_cmds)
 class ip_delete(_init_network, _optional_output_cmd):
     """Unreserve an IP (also delete the port, if attached)"""
 
diff --git a/kamaki/clients/compute/__init__.py b/kamaki/clients/compute/__init__.py
index 486013b..8bf927e 100644
--- a/kamaki/clients/compute/__init__.py
+++ b/kamaki/clients/compute/__init__.py
@@ -114,6 +114,7 @@ class ComputeClient(ComputeRestClient):
             metadata=None,
             personality=None,
             networks=None,
+            project=None,
             response_headers=dict(location=None)):
         """Submit request to create a new server
 
@@ -151,6 +152,9 @@ class ComputeClient(ComputeRestClient):
         if networks is not None:
             req['server']['networks'] = networks
 
+        if project:
+            req['server']['project'] = project
+
         r = self.servers_post(
             json_data=req,
             security_group=security_group,
diff --git a/kamaki/clients/cyclades/__init__.py b/kamaki/clients/cyclades/__init__.py
index 26a85e7..87f33c6 100644
--- a/kamaki/clients/cyclades/__init__.py
+++ b/kamaki/clients/cyclades/__init__.py
@@ -35,14 +35,14 @@ from kamaki.clients.cyclades.rest_api import CycladesRestClient
 from kamaki.clients.network import NetworkClient
 from kamaki.clients.utils import path4url
 from kamaki.clients import ClientError, Waiter
-
+import json
 
 class CycladesClient(CycladesRestClient, Waiter):
     """Synnefo Cyclades Compute API client"""
 
     def create_server(
             self, name, flavor_id, image_id,
-            metadata=None, personality=None, networks=None):
+            metadata=None, personality=None, networks=None, project=None):
         """Submit request to create a new server
 
         :param name: (str)
@@ -64,6 +64,8 @@ class CycladesClient(CycladesRestClient, Waiter):
             ATTENTION: Empty list is different to None. None means ' do not
             mention it', empty list means 'automatically get an ip'
 
+        :param project: the project where to assign the server
+
         :returns: a dict with the new virtual server details
 
         :raises ClientError: wraps request errors
@@ -78,7 +80,8 @@ class CycladesClient(CycladesRestClient, Waiter):
 
         return super(CycladesClient, self).create_server(
             name, flavor_id, image_id,
-            metadata=metadata, personality=personality, networks=networks)
+            metadata=metadata, personality=personality, networks=networks,
+            project=project)
 
     def set_firewall_profile(self, server_id, profile, port_id):
         """Set the firewall profile for the public interface of a server
@@ -123,6 +126,11 @@ class CycladesClient(CycladesRestClient, Waiter):
         r = self.servers_action_post(server_id, json_data=req, success=200)
         return r.json['console']
 
+    def reassign_server(self, server_id, project):
+        req = {'reassign': {'project': project}}
+        r = self.servers_action_post(server_id, json_data=req, success=200)
+        return r.headers
+
     def get_server_stats(self, server_id):
         """
         :param server_id: integer (str or int)
@@ -180,15 +188,35 @@ class CycladesNetworkClient(NetworkClient):
         r = self.get(path, success=200)
         return r.json['networks']
 
-    def create_network(self, type, name=None, shared=None):
+    def create_network(self, type, name=None, shared=None, project=None):
         req = dict(network=dict(type=type, admin_state_up=True))
         if name:
             req['network']['name'] = name
         if shared not in (None, ):
             req['network']['shared'] = bool(shared)
+        if project is not None:
+            req['network']['project'] = project
         r = self.networks_post(json_data=req, success=201)
         return r.json['network']
 
+    def networks_action_post(
+            self, network_id='', json_data=None, success=202, **kwargs):
+        """POST base_url/networks/<network_id>/action
+
+        :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('networks', network_id, 'action')
+        return self.post(path, data=json_data, success=success, **kwargs)
+
+    def reassign_network(self, network_id, project):
+        req = {'reassign': {'project': project}}
+        r = self.networks_action_post(network_id, json_data=req, success=200)
+        return r.headers
+
     def list_ports(self, detail=None):
         path = path4url('ports', 'detail' if detail else '')
         r = self.get(path, success=200)
@@ -215,6 +243,26 @@ class CycladesNetworkClient(NetworkClient):
         r = self.ports_post(json_data=dict(port=port), success=201)
         return r.json['port']
 
-    def create_floatingip(self, floating_network_id, floating_ip_address=''):
+    def create_floatingip(self, floating_network_id, floating_ip_address='',
+                          project=None):
+        args = {"project": project}
         return super(CycladesNetworkClient, self).create_floatingip(
-            floating_network_id, floating_ip_address=floating_ip_address)
+            floating_network_id,
+            floating_ip_address=floating_ip_address, args=args)
+
+    def floating_ip_action_post(
+            self, fip_id, json_data=None, success=202, **kwargs):
+        """POST base_url/floatingips/<fip_id>/action
+
+        :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('floatingips', fip_id, 'action')
+        return self.post(path, data=json_data, success=success, **kwargs)
+
+    def reassign_floating_ip(self, floating_network_id, project):
+        req = {'reassign': {'project': project}}
+        r = self.floating_ip_action_post(floating_network_id, json_data=req)
diff --git a/kamaki/clients/network/__init__.py b/kamaki/clients/network/__init__.py
index e06415f..92f8fd2 100644
--- a/kamaki/clients/network/__init__.py
+++ b/kamaki/clients/network/__init__.py
@@ -331,7 +331,8 @@ class NetworkClient(NetworkRestClient, Waiter):
 
     def create_floatingip(
             self, floating_network_id,
-            floating_ip_address='', port_id='', fixed_ip_address=''):
+            floating_ip_address='', port_id='', fixed_ip_address='',
+            args=None):
         """Cyclades do not use port_id and fixed_ip_address"""
         floatingip = dict(floating_network_id=floating_network_id)
         if floating_ip_address:
@@ -340,6 +341,8 @@ class NetworkClient(NetworkRestClient, Waiter):
             floatingip['port_id'] = port_id
         if fixed_ip_address:
             floatingip['fixed_ip_address'] = fixed_ip_address
+        if args is not None:
+            floatingip.update(args)
         r = self.floatingips_post(
             json_data=dict(floatingip=floatingip), success=200)
         return r.json['floatingip']
