commit 0dae1b9ffd1f5b84d7d9785ed77db13845ff76b8
Author: Dionysis Grigoropoulos <dgrig@grnet.gr>
Date:   Fri Sep 27 17:24:53 2013 +0300

    cyclades: Neutron API add subnet tests, bug fixes

diff --git a/snf-cyclades-app/synnefo/neutron/models.py b/snf-cyclades-app/synnefo/neutron/models.py
index eafe33f..399c58a 100644
--- a/snf-cyclades-app/synnefo/neutron/models.py
+++ b/snf-cyclades-app/synnefo/neutron/models.py
@@ -216,11 +216,9 @@ class Network(models.Model):
 class Subnet(models.Model):
     SUBNET_NAME_LENGTH = 128
 
-    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=SUBNET_NAME_LENGTH)
+    name = models.CharField('Network Name', max_length=SUBNET_NAME_LENGTH,
+                            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)
@@ -228,8 +226,6 @@ class Subnet(models.Model):
 
     # Synnefo related fields
     # subnet6 will be null for IPv4 only networks
-    subnet6 = models.CharField('IPv6 Subnet', max_length=64, null=True)
-    gateway6 = models.CharField('IPv6 Gateway', max_length=64, null=True)
     #pool = models.OneToOneField('IPPoolTable', related_name='network',
     #                            default=lambda: IPPoolTable.objects.create(
     #                                                        available_map='',
@@ -237,6 +233,9 @@ class Subnet(models.Model):
     #                                                        size=0),
     #                           null=True)
 
+    def __unicode__(self):
+        return "<Subnet %s>" % str(self.id)
+
 
 class NetworkInterface(models.Model):
     STATES = (
diff --git a/snf-cyclades-app/synnefo/neutron/models_factory.py b/snf-cyclades-app/synnefo/neutron/models_factory.py
index 23f24e7..0d4b9c0 100644
--- a/snf-cyclades-app/synnefo/neutron/models_factory.py
+++ b/snf-cyclades-app/synnefo/neutron/models_factory.py
@@ -1,5 +1,7 @@
 import factory
 import models
+from random import choice
+from string import letters, digits
 from synnefo.db.models_factory import VirtualMachineFactory
 
 
@@ -21,6 +23,11 @@ def round_seq_first(x):
     return lambda n: x[int(n) % size][0]
 
 
+def random_string(x):
+    '''Returns a random string of length x'''
+    return ''.join([choice(digits + letters) for i in range(x)])
+
+
 class NetworkFactory(factory.DjangoModelFactory):
     FACTORY_FOR = models.Network
 
@@ -43,3 +50,13 @@ class NetworkInterfaceFactory(factory.DjangoModelFactory):
 
     machine = factory.SubFactory(VirtualMachineFactory)
     network = factory.SubFactory(NetworkFactory)
+
+
+class SubnetFactory(factory.DjangoModelFactory):
+    FACTORY_FOR = models.Subnet
+
+    name = factory.LazyAttribute(lambda self: random_string(30))
+    ipversion = 4
+    cidr = factory.Sequence(lambda n: '192.168.{0}.0/24'.format(n))
+    dhcp = True
+    gateway = factory.Sequence(lambda n: '192.168.{0}.1/24'.format(n))
diff --git a/snf-cyclades-app/synnefo/neutron/network_views.py b/snf-cyclades-app/synnefo/neutron/network_views.py
index d0a5d47..d21a0c8 100644
--- a/snf-cyclades-app/synnefo/neutron/network_views.py
+++ b/snf-cyclades-app/synnefo/neutron/network_views.py
@@ -236,7 +236,7 @@ def network_to_dict(network):
     d['status'] = network.state
     d['public'] = network.public
     d['admin_state_up'] = "true"
-    subnet_cidr = [s.subnet_id for s in network.subnet_set.all()]
+    subnet_cidr = [s.id for s in network.subnet_set.all()]
     d['subnets'] = subnet_cidr
     return d
 
diff --git a/snf-cyclades-app/synnefo/neutron/subnet_views.py b/snf-cyclades-app/synnefo/neutron/subnet_views.py
index bce72f8..803da77 100644
--- a/snf-cyclades-app/synnefo/neutron/subnet_views.py
+++ b/snf-cyclades-app/synnefo/neutron/subnet_views.py
@@ -39,10 +39,10 @@ from django.http import HttpResponse
 from django.utils import simplejson as json
 
 from snf_django.lib.api import utils
-from models import Subnet
+from models import Subnet, Network
 from synnefo.logic import networks
 
-import ipaddr
+from ipaddr import IPv4Network, IPv6Network
 
 log = getLogger(__name__)
 
@@ -56,13 +56,13 @@ def demux(request):
         return api.api_method_not_allowed(request)
 
 
-def subnet_demux(request, offset):
+def subnet_demux(request, sub_id):
     if request.method == 'GET':
-        return get_subnet(request, offset)
+        return get_subnet(request, sub_id)
     elif request.method == 'DELETE':
-        return delete_subnet(request, offset)
+        return delete_subnet(request, sub_id)
     elif request.method == 'PUT':
-        return update_subnet(request, offset)
+        return update_subnet(request, sub_id)
     else:
         return api.api_method_not_allowed(request)
 
@@ -73,8 +73,8 @@ def list_subnets(request):
     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')]
+    subnets_dict = [subnet_to_dict(sub)
+                    for sub in user_subnets.order_by('id')]
     data = json.dumps({'subnets': subnets_dict})
 
     return HttpResponse(data, status=200)
@@ -84,96 +84,103 @@ def list_subnets(request):
 def create_subnet(request):
     '''Create a subnet'''
 
-    dic = utils.get_request_dict(request)
-    log.info('create subnet %s', dic)
+    dictionary = utils.get_request_dict(request)
+    log.info('create subnet %s', dictionary)
     user_id = request.user_uniq
 
     try:
-        subnet = dic['subnet']
+        subnet = dictionary['subnet']
         network_id = subnet['network_id']
         cidr = subnet['cidr']
     except KeyError:
         raise api.faults.BadRequest("Malformed request")
 
+    try:
+        network = Network.objects.get(id=network_id)
+    except Network.DoesNotExist:
+        raise api.faults.ItemNotFound("No networks found with that id")
+
+    if user_id != network.userid:
+        raise api.faults.Unauthorized("Unauthorized operation")
+
     ipversion = subnet.get('ip_version', 4)
     if ipversion not in [4, 6]:
         raise api.faults.BadRequest("Malformed IP version type")
 
     dhcp = subnet.get('enable_dhcp', True)
     if dhcp not in [True, False]:
-        raise api.faults.BadRequest("Malformed request, enable_dhcp must be"
-                                    " True or False")
+        raise api.faults.BadRequest("Malformed request, enable_dhcp must be "
+                                    "True or False")
     name = subnet.get('name', None)
     if len(str(name)) > Subnet.SUBNET_NAME_LENGTH:
         raise api.faults.BadRequest("Subnet name too long")
 
-    # FIX ME, SNF:gateway6 vs gateway6
-    gateway6 = subnet.get('SNF:gateway6', None)
-    subnet6 = subnet.get('SNF:subnet6', None)
-
     # Returns the first available IP in the subnet
-    potential_gateway = ipaddr.IPv4Network(cidr).network + 1
-    gateway = subnet.get('gateway_ip', potential_gateway)
+    if ipversion == 6:
+        potential_gateway = IPv6Network(cidr).network + 1
+        check_number_of_subnets(network, 6)
+    else:
+        potential_gateway = IPv4Network(cidr).network + 1
+        check_number_of_subnets(network, 4)
 
-    networks.validate_network_params(cidr, gateway, subnet6, gateway6)
+    gateway = subnet.get('gateway_ip', potential_gateway)
+    networks.validate_network_params(cidr, gateway)
 
     # FIX ME
     try:
-        sub = Subnet.objects.create(name=name, network_id=network_id,
-                                    cidr=cidr, ipversion=ipversion,
-                                    gateway=gateway, gateway6=gateway6,
-                                    subnet6=subnet6)
+        sub = Subnet.objects.create(name=name, network=network, cidr=cidr,
+                                    ipversion=ipversion, gateway=gateway,
+                                    dhcp=dhcp)
     except:
-        print "Error"
+        return "Error"
 
-    return HttpResponse("test")
+    return HttpResponse(sub, status=200)
 
 
 @api.api_method(http_method='GET', user_required=True, logger=log)
-def get_subnet(request, offset):
+def get_subnet(request, sub_id):
     '''Show info of a specific subnet'''
+    log.debug('get_subnet %s', sub_id)
+    user_id = request.user_uniq
+
     try:
-        subnet = Subnet.objects.get(subnet_id=offset)
+        subnet = Subnet.objects.get(id=sub_id)
     except Subnet.DoesNotExist:
         raise api.faults.ItemNotFound("Subnet not found")
 
-    subnet_dic = subnet_to_dict(subnet)
-    data = json.dumps({'subnet': subnet_dic})
+    if subnet.network.userid != user_id:
+        raise api.failts.Unauthorized("You're not allowed to view this subnet")
+
+    subnet_dict = subnet_to_dict(subnet)
+    data = json.dumps({'subnet': subnet_dict})
     return HttpResponse(data, status=200)
 
 
 @api.api_method(http_method='DELETE', user_required=True, logger=log)
-def delete_subnet(request, offset):
-    '''Delete a subnet'''
-    # Commented until we have a design document
-    #log.info('delete_subnet %s', offset)
-    #try:
-    #    subnet = Subnet.objects.get(subnet_id=offset)
-    #except Subnet.DoesNotExist:
-    #    raise api.faults.ItemNotFound("Subnet not found")
-        # Add support for 409 error, subnet in use
-
-    #subnets.delete()
-    #return HttpResponse(status=204)
+def delete_subnet(request, sub_id):
+    '''Delete a subnet -- Operation not allowed'''
     raise api.faults.BadRequest("Deletion of a subnet is not supported")
 
 
+@api.api_method(http_method='PUT', user_required=True, logger=log)
+def update_subnet(request, sub_id):
+    '''Update info of a subnet'''
+
+
 def subnet_to_dict(subnet):
     '''Returns a dictionary containing the info of a subnet'''
     # FIX ME, allocation pools
-    dic = dict({'id': subnet.sunbet_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': [], 'host_routes': [],
-                'allocation_pools': [], 'SNF:gateway6': subnet.gateway6,
-                'SNF:subnet6': subnet.subnet6})
-
-#    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=[], allocation_pools=[], host_routes=[],
-#               snf-gateway6=subnet.gateway6, snf-subnet6=subnet.subnet6)
-
-    return dic
+    dictionary = dict({'id': 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': [],
+                       'host_routes': [], 'allocation_pools': []})
+    return dictionary
+
+
+def check_number_of_subnets(network, version):
+    '''Checks if a user can add a subnet in a network'''
+    if network.subnet_set.filter(ipversion=version):
+        raise api.faults.BadRequest("Only one subnet of IPv4/IPv6 per "
+                                    "network is allowed")
diff --git a/snf-cyclades-app/synnefo/neutron/tests/api.py b/snf-cyclades-app/synnefo/neutron/tests/api.py
index 9f8d766..bdb03b8 100644
--- a/snf-cyclades-app/synnefo/neutron/tests/api.py
+++ b/snf-cyclades-app/synnefo/neutron/tests/api.py
@@ -116,91 +116,127 @@ class NetworkTest(BaseAPITest):
 
 class SubnetTest(BaseAPITest):
     def test_list_subnets(self):
-        '''Test Subnet list'''
+        '''Test list subnets without data'''
         response = self.get(SUBNETS_URL)
         self.assertSuccess(response)
         subnets = json.loads(response.content)
         self.assertEqual(subnets, {'subnets': []})
 
+    def test_list_subnets_data(self):
+        '''Test list subnets with data'''
+        test_net = mf.NetworkFactory()
+        test_subnet_ipv4 = mf.SubnetFactory(network=test_net)
+        test_subnet_ipv6 = mf.SubnetFactory(network=test_net, ipversion=6,
+                                            cidr='2620:0:2d0:200::7/32')
+        response = self.get(SUBNETS_URL, user=test_net.userid)
+        self.assertSuccess(response)
+
     def test_get_subnet(self):
-        '''Test get info of a subnet'''
-        url = join_urls(SUBNETS_URL, '42')
-        response = self.get(url)
+        '''Test get info of a single subnet'''
+        test_net = mf.NetworkFactory()
+        test_subnet = mf.SubnetFactory(network=test_net)
+        url = join_urls(SUBNETS_URL, str(test_subnet.id))
+        response = self.get(url, user=test_net.userid)
         self.assertSuccess(response)
-        subnet = json.loads(response.content)
-        self.assertEqual(subnet, {'subnet': []})
 
     def test_get_subnet_404(self):
         '''Test get info of a subnet that doesn't exist'''
-        url = join_urls(SUBNETS_URL, '52')
+        url = join_urls(SUBNETS_URL, '42')
         response = self.get(url)
         self.assertItemNotFound(response)
 
-    #def test_subnet_delete_not_found(self):
-     #   '''Test delete a subnet that doesn't exist'''
-     #   # FIX ME
-     #   url = join_urls(SUBNETS_URL, '52')
-     #   response = self.get(url)
-     #   self.assertItemNotFound(response)
-
     def test_subnet_delete(self):
         '''Test delete a subnet -- not supported'''
         url = join_urls(SUBNETS_URL, '42')
-        response = self.get(url)
+        response = self.delete(url)
+        self.assertBadRequest(response)
+
+   # def test_create_subnet_success(self):
+   #     '''Test create a subnet successfully'''
+   #     test_net = mf.NetworkFactory()
+   #     request = {
+   #         'subnet': {
+   #             'network_id': test_net.id,
+   #             'cidr': '10.0.3.0/24',
+   #             'ip_version': 4}
+   #     }
+   #     response = self.post(SUBNETS_URL, test_net.userid,
+   #                          json.dumps(request), "json")
+   #     self.assertSuccess(response)
+
+    def test_create_subnet_with_invalid_network_id(self):
+        '''Test create a subnet with a network id that doesn't exist'''
+        test_net = mf.NetworkFactory()
+        request = {
+            'subnet': {
+                'network_id': '42',
+                'cidr': '10.0.3.0/24',
+                'ip_version': 4}
+        }
+        response = self.post(SUBNETS_URL, test_net.userid, json.dumps(request),
+                             "json")
         self.assertItemNotFound(response)
 
     def test_create_subnet_with_malformed_ipversion(self):
         '''Create a subnet with a malformed ip_version type'''
+        test_net = mf.NetworkFactory()
         request = {
             'subnet': {
-                'network_id': 'ed2e3c10-2e43-4297-9006-2863a2d1abbc',
+                'network_id': test_net.id,
                 'cidr': '10.0.3.0/24',
                 'ip_version': 8}
         }
-        response = self.post(SUBNETS_URL, "user9", json.dumps(request), "json")
+        response = self.post(SUBNETS_URL, test_net.userid, json.dumps(request),
+                             "json")
         self.assertBadRequest(response)
 
     def test_create_subnet_with_invalid_cidr(self):
         '''Create a subnet with an invalid cidr'''
+        test_net = mf.NetworkFactory()
         request = {
             'subnet': {
-                'network_id': 'ed2e3c10-2e43-4297-9006-2863a2d1abbc',
+                'network_id': test_net.id,
                 'cidr': '192.168.3.0/8'}
         }
-        response = self.post(SUBNETS_URL, "user9", json.dumps(request), "json")
+        response = self.post(SUBNETS_URL, test_net.userid, json.dumps(request),
+                             "json")
         self.assertBadRequest(response)
 
     def test_create_subnet_with_invalid_gateway(self):
         '''Create a subnet with a gateway outside of the subnet range'''
+        test_net = mf.NetworkFactory()
         request = {
             'subnet': {
-                'network_id': 'ed2e3c10-2e43-4297-9006-2863a2d1abbc',
+                'network_id': test_net.id,
                 'cidr': '192.168.3.0/24',
                 'gateway_ip': '192.168.0.1'}
         }
-        response = self.post(SUBNETS_URL, "user9", json.dumps(request), "json")
+        response = self.post(SUBNETS_URL, test_net.userid, json.dumps(request),
+                             "json")
         self.assertBadRequest(response)
 
-    def test_create_subnet_with_long_name(self):
+    def test_create_subnet_with_invalid_name(self):
         '''Create a subnet with an invalid subnet name'''
+        test_net = mf.NetworkFactory()
         request = {
             'subnet': {
-                'network_id': 'ed2e3c10-2e43-4297-9006-2863a2d1abbc',
+                'network_id': test_net.id,
                 'cidr': '192.168.3.0/24',
-                'name': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
-                        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
-                        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'}
+                'name': 'a' * 300}
         }
-        response = self.post(SUBNETS_URL, "user9", json.dumps(request), "json")
+        response = self.post(SUBNETS_URL, test_net.userid, json.dumps(request),
+                             "json")
         self.assertBadRequest(response)
 
     def test_create_subnet_with_invalid_dhcp(self):
         '''Create a subnet with an invalid dhcp value'''
+        test_net = mf.NetworkFactory()
         request = {
             'subnet': {
-                'network_id': 'ed2e3c10-2e43-4297-9006-2863a2d1abbc',
+                'network_id': test_net.id,
                 'cidr': '192.168.3.0/24',
                 'enable_dhcp': 'None'}
         }
-        response = self.post(SUBNETS_URL, "user9", json.dumps(request), "json")
+        response = self.post(SUBNETS_URL, test_net.userid, json.dumps(request),
+                             "json")
         self.assertBadRequest(response)
