|
@@ -1,7 +1,8 @@
|
|
|
# -*- coding: utf-8 -*-
|
|
|
from django.db import models
|
|
|
+from django.core.exceptions import ValidationError
|
|
|
from netfields import CidrAddressField, NetManager
|
|
|
-
|
|
|
+from netaddr import IPNetwork, IPSet
|
|
|
|
|
|
class IPPool(models.Model):
|
|
|
"""Pool of IP addresses (either v4 or v6)."""
|
|
@@ -17,11 +18,37 @@ class IPPool(models.Model):
|
|
|
|
|
|
|
|
|
class IPSubnet(models.Model):
|
|
|
- inet = CidrAddressField()
|
|
|
+ inet = CidrAddressField(blank=True, verbose_name="Leave empty for automatic allocation")
|
|
|
objects = NetManager()
|
|
|
ip_pool = models.ForeignKey(IPPool)
|
|
|
offer_subscription = models.ForeignKey('offers.OfferSubscription',
|
|
|
related_name='ip_subnet')
|
|
|
|
|
|
+ def clean(self):
|
|
|
+ # TODO: define default subnet sizes for IPv4 / IPv6. Or better,
|
|
|
+ # integrate it in the pool definition.
|
|
|
+ if not self.inet:
|
|
|
+ # Automatically allocate a free subnet
|
|
|
+ pool = IPSet([self.ip_pool.inet])
|
|
|
+ used = IPSet((s.inet for s in self.ip_pool.ipsubnet_set.all()))
|
|
|
+ free = pool.difference(used)
|
|
|
+ SUBNET_SIZE = 32 if self.ip_pool.inet.version == 4 else 56
|
|
|
+ # Generator for efficiency (we don't build the whole list)
|
|
|
+ available = (p for p in free.iter_cidrs() if p.prefixlen <= SUBNET_SIZE)
|
|
|
+ # TODO: for IPv4, get rid of the network and broadcast
|
|
|
+ # addresses? Not really needed nowadays, and we usually don't
|
|
|
+ # have a real subnet in practice (i.e. Ethernet segment), but
|
|
|
+ # many /32.
|
|
|
+ try:
|
|
|
+ first_free = available.next()
|
|
|
+ except StopIteration:
|
|
|
+ raise ValidationError('Unable to allocate an IP subnet in the specified pool: not enough space left.')
|
|
|
+ self.inet = first_free.subnet(SUBNET_SIZE, 1).next()
|
|
|
+ else:
|
|
|
+ # TODO:
|
|
|
+ # Check that we are included in the IP pool.
|
|
|
+ # Check that we don't conflict with existing subnets
|
|
|
+ pass
|
|
|
+
|
|
|
def __unicode__(self):
|
|
|
return str(self.inet)
|