models.py 3.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. # -*- coding: utf-8 -*-
  2. from django.db import models
  3. from django.core.exceptions import ValidationError
  4. from django.core.validators import MaxValueValidator
  5. from netfields import CidrAddressField, NetManager
  6. from netaddr import IPNetwork, IPSet
  7. def validate_subnet(cidr):
  8. """Checks that a CIDR object is indeed a subnet, i.e. the host bits are
  9. all set to zero."""
  10. if not isinstance(cidr, IPNetwork):
  11. raise ValidationError("Internal error, expected IPNetwork object")
  12. if cidr.ip != cidr.network:
  13. raise ValidationError("{} is not a proper subnet, you probably mean {}".format(cidr, cidr.cidr))
  14. class IPPool(models.Model):
  15. """Pool of IP addresses (either v4 or v6)."""
  16. name = models.CharField(max_length=255, blank=False, null=False,
  17. verbose_name='Name of the IP pool')
  18. default_subnetsize = models.PositiveSmallIntegerField(blank=False,
  19. verbose_name='Default subnet size to allocate to subscribers in this pool',
  20. validators=[MaxValueValidator(64)])
  21. inet = CidrAddressField(validators=[validate_subnet])
  22. objects = NetManager()
  23. def clean(self):
  24. if self.inet:
  25. max_subnetsize = 64 if self.inet.version == 6 else 32
  26. if not self.inet.prefixlen <= self.default_subnetsize <= max_subnetsize:
  27. raise ValidationError('Invalid default subnet size')
  28. def __unicode__(self):
  29. return self.name
  30. class IPSubnet(models.Model):
  31. inet = CidrAddressField(blank=True, validators=[validate_subnet],
  32. verbose_name="Leave empty for automatic allocation")
  33. objects = NetManager()
  34. ip_pool = models.ForeignKey(IPPool)
  35. offer_subscription = models.ForeignKey('offers.OfferSubscription',
  36. related_name='ip_subnet')
  37. def clean(self):
  38. if not self.inet:
  39. # Automatically allocate a free subnet
  40. pool = IPSet([self.ip_pool.inet])
  41. used = IPSet((s.inet for s in self.ip_pool.ipsubnet_set.all()))
  42. free = pool.difference(used)
  43. # Generator for efficiency (we don't build the whole list)
  44. available = (p for p in free.iter_cidrs() if p.prefixlen <= self.ip_pool.default_subnetsize)
  45. # TODO: for IPv4, get rid of the network and broadcast
  46. # addresses? Not really needed nowadays, and we usually don't
  47. # have a real subnet in practice (i.e. Ethernet segment), but
  48. # many /32.
  49. try:
  50. first_free = available.next()
  51. except StopIteration:
  52. raise ValidationError('Unable to allocate an IP subnet in the specified pool: not enough space left.')
  53. self.inet = first_free.subnet(SUBNET_SIZE, 1).next()
  54. else:
  55. # Check that we are included in the IP pool.
  56. if not self.inet in self.ip_pool.inet:
  57. raise ValidationError('Subnet must be included in the IP pool.')
  58. # Check that we don't conflict with existing subnets.
  59. # TODO: use precise database query instead of querying all
  60. # subnets and filtering in Python.
  61. existing = IPSet((s.inet for s in self.ip_pool.ipsubnet_set.all()))
  62. intersection = subnet.intersection(existing)
  63. if intersection.size:
  64. raise ValidationError('Subnet must not intersect with existing subnets.\nIntersected subnets: {}.'.format([str(p) for p in intersection.iter_cidrs()]))
  65. def __unicode__(self):
  66. return str(self.inet)