asns.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. from django.core.exceptions import ValidationError
  2. from django.db import models
  3. from django.utils.translation import gettext_lazy as _
  4. from ipam.fields import ASNField
  5. from ipam.querysets import ASNRangeQuerySet
  6. from netbox.models import OrganizationalModel, PrimaryModel
  7. from netbox.models.features import ContactsMixin
  8. __all__ = (
  9. 'ASN',
  10. 'ASNRange',
  11. )
  12. class ASNRange(OrganizationalModel):
  13. name = models.CharField(
  14. verbose_name=_('name'),
  15. max_length=100,
  16. unique=True,
  17. db_collation="natural_sort"
  18. )
  19. slug = models.SlugField(
  20. verbose_name=_('slug'),
  21. max_length=100,
  22. unique=True
  23. )
  24. rir = models.ForeignKey(
  25. to='ipam.RIR',
  26. on_delete=models.PROTECT,
  27. related_name='asn_ranges',
  28. verbose_name=_('RIR')
  29. )
  30. start = ASNField(
  31. verbose_name=_('start'),
  32. )
  33. end = ASNField(
  34. verbose_name=_('end'),
  35. )
  36. tenant = models.ForeignKey(
  37. to='tenancy.Tenant',
  38. on_delete=models.PROTECT,
  39. related_name='asn_ranges',
  40. blank=True,
  41. null=True
  42. )
  43. objects = ASNRangeQuerySet.as_manager()
  44. class Meta:
  45. ordering = ('name',)
  46. verbose_name = _('ASN range')
  47. verbose_name_plural = _('ASN ranges')
  48. def __str__(self):
  49. return f'{self.name} ({self.range_as_string()})'
  50. def clean(self):
  51. super().clean()
  52. if self.end <= self.start:
  53. raise ValidationError(
  54. _("Starting ASN ({start}) must be lower than ending ASN ({end}).").format(
  55. start=self.start, end=self.end
  56. )
  57. )
  58. @property
  59. def range(self):
  60. """
  61. Return a range of integers representing the ASN range.
  62. """
  63. return range(self.start, self.end + 1)
  64. @property
  65. def start_asdot(self):
  66. """
  67. Return ASDOT notation for AS numbers greater than 16 bits.
  68. """
  69. return ASNField.to_asdot(self.start)
  70. @property
  71. def end_asdot(self):
  72. """
  73. Return ASDOT notation for AS numbers greater than 16 bits.
  74. """
  75. return ASNField.to_asdot(self.end)
  76. def range_as_string(self):
  77. """
  78. Return a string representation of the ASN range.
  79. """
  80. return f'{self.start}-{self.end}'
  81. def range_as_string_with_asdot(self):
  82. """
  83. Return a string representation of the ASN range, including ASDOT notation.
  84. """
  85. if self.end >= 65536:
  86. return f'{self.range_as_string()} ({self.start_asdot}-{self.end_asdot})'
  87. return self.range_as_string()
  88. def get_child_asns(self):
  89. """
  90. Return all child ASNs (ASNs within the range).
  91. """
  92. return ASN.objects.filter(
  93. asn__gte=self.start,
  94. asn__lte=self.end
  95. )
  96. def get_available_asns(self):
  97. """
  98. Return all available ASNs within this range.
  99. """
  100. range = set(self.range)
  101. existing_asns = set(self.get_child_asns().values_list('asn', flat=True))
  102. available_asns = sorted(range - existing_asns)
  103. return available_asns
  104. class ASN(ContactsMixin, PrimaryModel):
  105. """
  106. An autonomous system (AS) number is typically used to represent an independent routing domain. A site can have
  107. one or more ASNs assigned to it.
  108. """
  109. rir = models.ForeignKey(
  110. to='ipam.RIR',
  111. on_delete=models.PROTECT,
  112. related_name='asns',
  113. verbose_name=_('RIR'),
  114. help_text=_("Regional Internet Registry responsible for this AS number space")
  115. )
  116. asn = ASNField(
  117. unique=True,
  118. verbose_name=_('ASN'),
  119. help_text=_('16- or 32-bit autonomous system number')
  120. )
  121. tenant = models.ForeignKey(
  122. to='tenancy.Tenant',
  123. on_delete=models.PROTECT,
  124. related_name='asns',
  125. blank=True,
  126. null=True
  127. )
  128. prerequisite_models = (
  129. 'ipam.RIR',
  130. )
  131. class Meta:
  132. ordering = ['asn']
  133. verbose_name = _('ASN')
  134. verbose_name_plural = _('ASNs')
  135. def __str__(self):
  136. return f'AS{self.asn_with_asdot}'
  137. @property
  138. def asn_asdot(self):
  139. """
  140. Return ASDOT notation for AS numbers greater than 16 bits.
  141. """
  142. return ASNField.to_asdot(self.asn)
  143. @property
  144. def asn_with_asdot(self):
  145. """
  146. Return both plain and ASDOT notation, where applicable.
  147. """
  148. if self.asn >= 65536:
  149. return f'{self.asn} ({self.asn_asdot})'
  150. return str(self.asn)
  151. @property
  152. def prefixed_name(self):
  153. """
  154. Return the ASN with ASDOT notation prefixed with "AS".
  155. """
  156. return f'AS{self.asn_with_asdot}'