commit 7eae8fced1858e04a8c0855aaf79e501c82966ec
Author: Dionysis Grigoropoulos <dgrig@grnet.gr>
Date:   Mon Sep 23 12:41:20 2013 +0300

    cyclades: Neutron api, poc subnet implementation

diff --git a/snf-cyclades-app/synnefo/neutron/models.py b/snf-cyclades-app/synnefo/neutron/models.py
index 21bd3f8..f4936eb 100644
--- a/snf-cyclades-app/synnefo/neutron/models.py
+++ b/snf-cyclades-app/synnefo/neutron/models.py
@@ -51,7 +51,6 @@ import logging
 log = logging.getLogger(__name__)
 
 
-
 class Network(models.Model):
     OPER_STATES = (
         ('PENDING', 'Pending'),  # Unused because of lazy networks
@@ -124,10 +123,12 @@ class Network(models.Model):
     state = models.CharField(choices=OPER_STATES, max_length=32,
                              default='PENDING')
     machines = models.ManyToManyField(VirtualMachine,
-                                      through='NetworkInterface', related_name='neutron_machines')
+                                      through='NetworkInterface',
+                                      related_name='neutron_machines')
     action = models.CharField(choices=ACTIONS, max_length=32, null=True,
                               default=None)
-    drained = models.BooleanField("Drained", default=False, null=False) # this is the opposite of admin_state_up
+    drained = models.BooleanField("Drained", default=False, null=False)
+    # this is the opposite of admin_state_up
     floating_ip_pool = models.BooleanField('Floating IP Pool', null=False,
                                            default=False)
     #serial = models.ForeignKey(QuotaHolderSerial, related_name='network',
@@ -211,19 +212,26 @@ class Network(models.Model):
         def __str__(self):
             return repr(str(self._action))
 
+
 class Subnet(models.Model):
+    subnet_id = models.CharField('ID of the subnet', max_length=128,
+                                 null=True, db_index=True, primary_key=True)
+    network = models.ForeignKey('Network')
 
     name = models.CharField('Network Name', max_length=128)
-    subnet_id = models.CharField('ID of the subnet', max_length=128,
-                              null=True, db_index=True)
-    network_id = models.ForeignKey('Network')
-    # subnet will be null for IPv6 only networks
-    subnet = models.CharField('Subnet', max_length=32, null=True)
+    ipversion = models.IntegerField('IP Version', default=4)
+    cidr = models.CharField('Subnet', max_length=32, null=True)
+    gateway = models.CharField('Gateway', max_length=32, null=True)
+    #dns = models.
+    #pools = models.
+    #hostroutes = models.
+    dhcp = models.BooleanField('DHCP', default=True)
+
+    # Synnefo related fields
     # subnet6 will be null for IPv4 only networks
     subnet6 = models.CharField('IPv6 Subnet', max_length=64, null=True)
-    gateway = models.CharField('Gateway', max_length=32, null=True)
     gateway6 = models.CharField('IPv6 Gateway', max_length=64, null=True)
-    dhcp = models.BooleanField('DHCP', default=True)
+    #dns_nameservers = models.
     #pool = models.OneToOneField('IPPoolTable', related_name='network',
     #                            default=lambda: IPPoolTable.objects.create(
     #                                                        available_map='',
@@ -231,6 +239,7 @@ class Subnet(models.Model):
     #                                                        size=0),
     #                           null=True)
 
+
 class NetworkInterface(models.Model):
     STATES = (
         ("ACTIVE", "Active"),
diff --git a/snf-cyclades-app/synnefo/neutron/subnet_views.py b/snf-cyclades-app/synnefo/neutron/subnet_views.py
index e28944a..81b1d3e 100644
--- a/snf-cyclades-app/synnefo/neutron/subnet_views.py
+++ b/snf-cyclades-app/synnefo/neutron/subnet_views.py
@@ -1,26 +1,61 @@
+# Copyright 2013 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from logging import getLogger
+from snf_django.lib import api
+from snf_django.lib.api import faults
+
 from django.http import HttpResponse
 from django.utils import simplejson as json
 
-from snf_django.lib import api
 from snf_django.lib.api import utils
 
-from models import Network
+from models import Subnet
 
-from logging import getLogger
+log = getLogger('synnefo.neutron')
 
-log = getLogger(__name__)
 
 def demux(request):
     if request.method == 'GET':
-        return list_networks(request)
-        #return HttpResponse("in subnet get")
+        return list_subnets(request)
     elif request.method == 'POST':
         #return create_network(request)
         return HttpResponse("in subnet POST")
     else:
         return api.api_method_not_allowed(request)
 
-def subnet_demux(request,offset):
+
+def subnet_demux(request, offset):
     if request.method == 'GET':
         #return get_network(request,offset)
         return HttpResponse("in subnet det get")
@@ -33,77 +68,113 @@ def subnet_demux(request,offset):
     else:
         return api.api_method_not_allowed(request)
 
+
 @api.api_method(http_method='GET', user_required=True, logger=log)
-def list_networks(request):
-    user_networks = Network.objects.all()
-    networks_dict = [network_to_dict(net)
-                    for net in user_networks.order_by('name')]
-    data = json.dumps({'networks': networks_dict})
+def list_subnets(request):
+    '''Placeholder'''
+    log.debug('list_subnets')
+
+    user_subnets = Subnet.objects.filter(network__userid=request.user_uniq)
+    subnets_dict = [subnet_to_dict(user_subnets)
+                    for net in user_subnets.order_by('name')]
+    data = json.dumps({'subnets': subnets_dict})
+
     return HttpResponse(data, status=200)
 
+
 @api.api_method(http_method='POST', user_required=True, logger=log)
-def create_network(request):
+def create_subnet(request):
+    '''Placeholder'''
     dic = utils.get_request_dict(request)
+
     try:
-    	net = dic["network"]
-    	name = net["name"]
-    	user_id = request.user_uniq
+        subnet = dic['subnet']
+        network_id = subnet['network_id']
+        cidr = subnet['cidr']
     except KeyError:
-        raise api.faults.BadRequest("Wrong data")
-    # get more info from the JSON
-    new_net = Network(name=name,user_id=user_id)
+        raise api.faults.BadRequest("Malformed request")
+
+    ipversion = subnet.get('ip_version', '4')
+    if ipversion != 4 or ipversion != 6:
+        raise api.faults.BadRequests("Malformed IP version type")
+    dhcp = subnet.get('enable_dhcp', True)
+    name = subnet.get('name', None)
+
+    # Returns the first available ip in the subnet
+    potential_gateway = ipaddr.IPv4Network(cidr).network + 1
+    gateway = subnet.get('gateway_ip', potential_gateway)
+
+    ddnew_net = Network(name=name, user_id=user_id)
     new_net.save()
     net_dic = network_to_dict(new_net)
-    data = json.dumps({'network':net_dic})
-    return HttpResponse("hello world")
+    data = json.dumps({'network': net_dic})
+
+    #try:
+    #    sub = Subnet.objects.create(name=name, network_id=network_id,
+    #                                cidr=cidr, ipversion=ipversion,
+    #                                gateway=gateway)
+    return HttpResponse("test world")
+    #return 1
 
 
 @api.api_method(http_method='GET', user_required=True, logger=log)
-def get_network(request,offset):
+def get_subnet(request, offset):
+    '''Placeholder'''
     try:
-        net = Network.objects.get(id=offset)
-    except Network.DoesNotExist:
-        raise api.faults.ItemNotFound("Network not found")
+        subnet = Subnet.objects.get(id=offset)
+    except Subnet.DoesNotExist:
+        raise api.faults.ItemNotFound("Subnet not found")
+
+    subnet_dic = subnet_to_dict(subnet)
+    data = json.dumps({'subnet': subnet_dic})
+    return HttpResponse(data, status=200)
 
-    net_dic = network_to_dict(net)
-    data = json.dumps({'network': net_dic})
-    return HttpResponse(data,status=200)
 
 @api.api_method(http_method='DELETE', user_required=True, logger=log)
-def delete_network(request,offset):
-   try:
-        net = Network.objects.get(id=offset)
-   except Network.DoesNotExist:
-        raise api.faults.ItemNotFound("Network not found")
+def delete_subnet(request, offset):
+    '''Placeholder'''
+    try:
+        subnet = Subnet.objects.get(id=offset)
+    except Subnet.DoesNotExist:
+        raise api.faults.ItemNotFound("Subnet not found")
+        # Add support for 409 error, subnet in use
+
+    subnet.delete()
+    return HttpResponse(status=204)
 
-   net.delete()
-   return HttpResponse(status=204)
 
 @api.api_method(http_method='PUT', user_required=True, logger=log)
-def update_network(request,offset):
+def update_network(request, offset):
     try:
         net = Network.objects.get(id=offset)
     except Network.DoesNotExist:
         raise api.faults.ItemNotFound("Network not found")
     info = utils.get_request_dict(request)
-    updatable = set(["name","driver"])
+    updatable = set(["name", "driver"])
     try:
-		new_vals = info["network"]
+        new_vals = info["network"]
     except Keyerror:
         raise api.faults.BadRequest()
 
-    for key,val in new_vals.iteritems():
+    for key, val in new_vals.iteritems():
         if key in updatable:
-            setattr(net,key,val)
+            setattr(net, key, val)
         else:
             raise api.faults.BadRequest()
-	net.save()
+        net.save()
     net_dic = network_to_dict(net)
-    data = json.dumps({"network":net_dic})
+    data = json.dumps({"network": net_dic})
     return HttpResponse(data)
 
-def network_to_dict(net):
-    d = dict(name=net.name,driver=net.driver,driver_ops=net.driver_ops,id=net.id,user_id=net.user_id)
-    return d
-
 
+def subnet_to_dict(subnet):
+    '''Returns a dictionary containing the info of a subnet'''
+    dic = dict(id=subnet.subnet_id, network_id=subnet.network.id,
+               name=subnet.name, tenant_id=subnet.network.userid,
+               gateway_ip=subnet.gateway, ip_version=subnet.ipversion,
+               cidr=subnet.cidr, enable_dhcp=subnet.dhcp,
+               dns_nameservers=subnet.dns)
+               # , allocation_pools=subnet.pools,
+               # host_routes=subneth.ostroutes, snf-gateway6=subnet.gateway6,
+               # snf-subnet6=subnet.subnet6)
+    return dic
diff --git a/snf-cyclades-app/synnefo/neutron/tests/api.py b/snf-cyclades-app/synnefo/neutron/tests/api.py
index bfe396b..1f29f73 100644
--- a/snf-cyclades-app/synnefo/neutron/tests/api.py
+++ b/snf-cyclades-app/synnefo/neutron/tests/api.py
@@ -7,6 +7,7 @@ from synnefo.lib import join_urls
 
 NEUTRON_URL = get_service_path(cyclades_services, "neutron", "v2.0")
 NETWORKS_URL = join_urls(NEUTRON_URL, "networks/")
+SUBNETS_URL = join_urls(NEUTRON_URL, "subnets/")
 
 
 class NetworkTest(BaseAPITest):
@@ -23,15 +24,24 @@ class NetworkTest(BaseAPITest):
         self.assertEqual(code, 501)
 
     def test_get_unfound_network(self):
-        url = join_urls(NETWORKS_URL,"123")
+        url = join_urls(NETWORKS_URL, "123")
         response = self.get(url)
         self.assertItemNotFound(response)
 
     def test_delete_unfound_network(self):
-        url = join_urls(NETWORKS_URL,"123")
+        url = join_urls(NETWORKS_URL, "123")
         response = self.delete(url)
         self.assertItemNotFound(response)
 
-
     def marios(self):
         print "hello world"
+
+
+class SubnetTest(BaseAPITest):
+    def test_list_subnets(self):
+        '''Test Subnet list'''
+        response = self.get(SUBNETS_URL)
+        print response
+        self.assertSuccess(response)
+        subnets = json.loads(response.content)
+        self.assertEqual(subnets, {'subnets': []})
