models.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. from netaddr import IPNetwork, cidr_merge
  2. from django.core.exceptions import ValidationError
  3. from django.core.urlresolvers import reverse
  4. from django.core.validators import MaxValueValidator, MinValueValidator
  5. from django.db import models
  6. from dcim.models import Interface
  7. from .fields import IPNetworkField, IPAddressField
  8. AF_CHOICES = (
  9. (4, 'IPv4'),
  10. (6, 'IPv6'),
  11. )
  12. BOOTSTRAP_CLASS_CHOICES = (
  13. (0, 'Default'),
  14. (1, 'Primary'),
  15. (2, 'Success'),
  16. (3, 'Info'),
  17. (4, 'Warning'),
  18. (5, 'Danger'),
  19. )
  20. class VRF(models.Model):
  21. """
  22. A discrete layer three forwarding domain (e.g. a routing table)
  23. """
  24. name = models.CharField(max_length=50)
  25. rd = models.CharField(max_length=21, unique=True, verbose_name='Route distinguisher')
  26. description = models.CharField(max_length=100, blank=True)
  27. class Meta:
  28. ordering = ['name']
  29. verbose_name = 'VRF'
  30. verbose_name_plural = 'VRFs'
  31. def __unicode__(self):
  32. return self.name
  33. def get_absolute_url(self):
  34. return reverse('ipam:vrf', args=[self.pk])
  35. class Status(models.Model):
  36. """
  37. The status of a prefix or VLAN (e.g. allocated, reserved, etc.)
  38. """
  39. name = models.CharField(max_length=50, unique=True)
  40. slug = models.SlugField(unique=True)
  41. weight = models.PositiveSmallIntegerField(default=1000)
  42. bootstrap_class = models.PositiveSmallIntegerField(choices=BOOTSTRAP_CLASS_CHOICES, default=0)
  43. class Meta:
  44. ordering = ['weight', 'name']
  45. verbose_name_plural = 'statuses'
  46. def __unicode__(self):
  47. return self.name
  48. class Role(models.Model):
  49. """
  50. The role of an address resource (e.g. customer, infrastructure, mgmt, etc.)
  51. """
  52. name = models.CharField(max_length=50, unique=True)
  53. slug = models.SlugField(unique=True)
  54. weight = models.PositiveSmallIntegerField(default=1000)
  55. class Meta:
  56. ordering = ['weight', 'name']
  57. def __unicode__(self):
  58. return self.name
  59. class RIR(models.Model):
  60. """
  61. A regional Internet registry (e.g. ARIN) or governing standard (e.g. RFC 1918)
  62. """
  63. name = models.CharField(max_length=50, unique=True)
  64. slug = models.SlugField(unique=True)
  65. class Meta:
  66. ordering = ['name']
  67. verbose_name = 'RIR'
  68. verbose_name_plural = 'RIRs'
  69. def __unicode__(self):
  70. return self.name
  71. class Aggregate(models.Model):
  72. """
  73. A top-level IPv4 or IPv6 prefix
  74. """
  75. family = models.PositiveSmallIntegerField(choices=AF_CHOICES)
  76. prefix = IPNetworkField()
  77. rir = models.ForeignKey('RIR', related_name='aggregates', on_delete=models.PROTECT, verbose_name='RIR')
  78. date_added = models.DateField(blank=True, null=True)
  79. description = models.CharField(max_length=100, blank=True)
  80. class Meta:
  81. ordering = ['family', 'prefix']
  82. def __unicode__(self):
  83. return str(self.prefix)
  84. def get_absolute_url(self):
  85. return reverse('ipam:aggregate', args=[self.pk])
  86. def clean(self):
  87. if self.prefix:
  88. # Clear host bits from prefix
  89. self.prefix = self.prefix.cidr
  90. # Ensure that the aggregate being added is not covered by an existing aggregate
  91. covering_aggregates = Aggregate.objects.filter(prefix__net_contains_or_equals=str(self.prefix))
  92. if self.pk:
  93. covering_aggregates = covering_aggregates.exclude(pk=self.pk)
  94. if covering_aggregates:
  95. raise ValidationError("{} is already covered by an existing aggregate ({})"
  96. .format(self.prefix, covering_aggregates[0]))
  97. def save(self, *args, **kwargs):
  98. if self.prefix:
  99. # Infer address family from IPNetwork object
  100. self.family = self.prefix.version
  101. super(Aggregate, self).save(*args, **kwargs)
  102. def get_utilization(self):
  103. """
  104. Determine the utilization rate of the aggregate prefix and return it as a percentage.
  105. """
  106. child_prefixes = Prefix.objects.filter(prefix__net_contained_or_equal=str(self.prefix))
  107. # Remove overlapping prefixes from list of children
  108. networks = cidr_merge([c.prefix for c in child_prefixes])
  109. children_size = float(0)
  110. for p in networks:
  111. children_size += p.size
  112. return int(children_size / self.prefix.size * 100)
  113. class PrefixQuerySet(models.QuerySet):
  114. def annotate_depth(self, limit=None):
  115. """
  116. Iterate through a QuerySet of Prefixes and annotate the hierarchical level of each. While it would be preferable
  117. to do this using .extra() on the QuerySet to count the unique parents of each prefix, that approach introduces
  118. performance issues at scale.
  119. Because we're adding a non-field attribute to the model, annotation must be made *after* any QuerySet
  120. modifications.
  121. """
  122. queryset = self
  123. stack = []
  124. for p in queryset:
  125. try:
  126. prev_p = stack[-1]
  127. except IndexError:
  128. prev_p = None
  129. if prev_p is not None:
  130. while (p.prefix not in prev_p.prefix) or p.prefix == prev_p.prefix:
  131. stack.pop()
  132. try:
  133. prev_p = stack[-1]
  134. except IndexError:
  135. prev_p = None
  136. break
  137. if prev_p is not None:
  138. prev_p.has_children = True
  139. stack.append(p)
  140. p.depth = len(stack) - 1
  141. if limit is None:
  142. return queryset
  143. return filter(lambda p: p.depth <= limit, queryset)
  144. class Prefix(models.Model):
  145. """
  146. An IPv4 or IPv6 prefix, including mask length
  147. """
  148. family = models.PositiveSmallIntegerField(choices=AF_CHOICES, editable=False)
  149. prefix = IPNetworkField()
  150. site = models.ForeignKey('dcim.Site', related_name='prefixes', on_delete=models.PROTECT, blank=True, null=True)
  151. vrf = models.ForeignKey('VRF', related_name='prefixes', on_delete=models.PROTECT, blank=True, null=True, verbose_name='VRF')
  152. vlan = models.ForeignKey('VLAN', related_name='prefixes', on_delete=models.PROTECT, blank=True, null=True, verbose_name='VLAN')
  153. status = models.ForeignKey('Status', related_name='prefixes', on_delete=models.PROTECT)
  154. role = models.ForeignKey('Role', related_name='prefixes', on_delete=models.SET_NULL, blank=True, null=True)
  155. description = models.CharField(max_length=100, blank=True)
  156. objects = PrefixQuerySet.as_manager()
  157. class Meta:
  158. ordering = ['family', 'prefix']
  159. verbose_name_plural = 'prefixes'
  160. def __unicode__(self):
  161. return str(self.prefix)
  162. def get_absolute_url(self):
  163. return reverse('ipam:prefix', args=[self.pk])
  164. def save(self, *args, **kwargs):
  165. if self.prefix:
  166. # Clear host bits from prefix
  167. self.prefix = self.prefix.cidr
  168. # Infer address family from IPNetwork object
  169. self.family = self.prefix.version
  170. super(Prefix, self).save(*args, **kwargs)
  171. @property
  172. def new_subnet(self):
  173. if self.family == 4:
  174. if self.prefix.prefixlen <= 30:
  175. return IPNetwork('{}/{}'.format(self.prefix.network, self.prefix.prefixlen + 1))
  176. return None
  177. if self.family == 6:
  178. if self.prefix.prefixlen <= 126:
  179. return IPNetwork('{}/{}'.format(self.prefix.network, self.prefix.prefixlen + 1))
  180. return None
  181. class IPAddress(models.Model):
  182. """
  183. An IPv4 or IPv6 address
  184. """
  185. family = models.PositiveSmallIntegerField(choices=AF_CHOICES, editable=False)
  186. address = IPAddressField()
  187. vrf = models.ForeignKey('VRF', related_name='ip_addresses', on_delete=models.PROTECT, blank=True, null=True, verbose_name='VRF')
  188. interface = models.ForeignKey(Interface, related_name='ip_addresses', on_delete=models.CASCADE, blank=True, null=True)
  189. nat_inside = models.OneToOneField('self', related_name='nat_outside', on_delete=models.SET_NULL, blank=True, null=True, verbose_name='NAT IP (inside)')
  190. description = models.CharField(max_length=100, blank=True)
  191. class Meta:
  192. ordering = ['family', 'address']
  193. verbose_name = 'IP address'
  194. verbose_name_plural = 'IP addresses'
  195. def __unicode__(self):
  196. return str(self.address)
  197. def get_absolute_url(self):
  198. return reverse('ipam:ipaddress', args=[self.pk])
  199. def save(self, *args, **kwargs):
  200. if self.address:
  201. # Infer address family from IPAddress object
  202. self.family = self.address.version
  203. super(IPAddress, self).save(*args, **kwargs)
  204. @property
  205. def device(self):
  206. if self.interface:
  207. return self.interface.device
  208. return None
  209. class VLAN(models.Model):
  210. """
  211. A VLAN within a site
  212. """
  213. site = models.ForeignKey('dcim.Site', related_name='vlans', on_delete=models.PROTECT)
  214. vid = models.PositiveSmallIntegerField(verbose_name='ID', validators=[
  215. MinValueValidator(1),
  216. MaxValueValidator(4094)
  217. ])
  218. name = models.CharField(max_length=30)
  219. status = models.ForeignKey('Status', related_name='vlans', on_delete=models.PROTECT)
  220. role = models.ForeignKey('Role', related_name='vlans', on_delete=models.SET_NULL, blank=True, null=True)
  221. class Meta:
  222. ordering = ['site', 'vid']
  223. verbose_name = 'VLAN'
  224. verbose_name_plural = 'VLANs'
  225. def __unicode__(self):
  226. return "{0} ({1})".format(self.vid, self.name)
  227. def get_absolute_url(self):
  228. return reverse('ipam:vlan', args=[self.pk])