# -*- coding: utf-8 -*- from django.db import models from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator from netfields import CidrAddressField, NetManager from netaddr import IPNetwork, IPSet class IPPool(models.Model): """Pool of IP addresses (either v4 or v6).""" name = models.CharField(max_length=255, blank=False, null=False, verbose_name='Name of the IP pool') default_subnetsize = models.PositiveSmallIntegerField(blank=False, verbose_name='Default subnet size to allocate to subscribers in this pool', validators=[MaxValueValidator(64)]) inet = CidrAddressField() objects = NetManager() def clean(self): if self.inet.version == 4: if not self.inet.prefixlen <= self.default_subnetsize <= 32: raise ValidationError('Invalid default subnet size') elif self.inet.version == 6: if not self.inet.prefixlen <= self.default_subnetsize <= 64: raise ValidationError('Invalid default subnet size') else: pass def __unicode__(self): return self.name class IPSubnet(models.Model): 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): 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) # Generator for efficiency (we don't build the whole list) available = (p for p in free.iter_cidrs() if p.prefixlen <= self.ip_pool.default_subnetsize) # 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)