2
0

virtual_circuits.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. from functools import cached_property
  2. from django.contrib.contenttypes.fields import GenericRelation
  3. from django.core.exceptions import ValidationError
  4. from django.db import models
  5. from django.urls import reverse
  6. from django.utils.translation import gettext_lazy as _
  7. from circuits.choices import *
  8. from netbox.models import ChangeLoggedModel, PrimaryModel
  9. from netbox.models.features import CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, TagsMixin
  10. from .base import BaseCircuitType
  11. __all__ = (
  12. 'VirtualCircuit',
  13. 'VirtualCircuitTermination',
  14. 'VirtualCircuitType',
  15. )
  16. class VirtualCircuitType(BaseCircuitType):
  17. """
  18. Like physical circuits, virtual circuits can be organized by their functional role. For example, a user might wish
  19. to categorize virtual circuits by their technological nature or by product name.
  20. """
  21. class Meta:
  22. ordering = ('name',)
  23. verbose_name = _('virtual circuit type')
  24. verbose_name_plural = _('virtual circuit types')
  25. class VirtualCircuit(PrimaryModel):
  26. """
  27. A virtual connection between two or more endpoints, delivered across one or more physical circuits.
  28. """
  29. cid = models.CharField(
  30. max_length=100,
  31. verbose_name=_('circuit ID'),
  32. help_text=_('Unique circuit ID')
  33. )
  34. provider_network = models.ForeignKey(
  35. to='circuits.ProviderNetwork',
  36. on_delete=models.PROTECT,
  37. related_name='virtual_circuits'
  38. )
  39. provider_account = models.ForeignKey(
  40. to='circuits.ProviderAccount',
  41. on_delete=models.PROTECT,
  42. related_name='virtual_circuits',
  43. blank=True,
  44. null=True
  45. )
  46. type = models.ForeignKey(
  47. to='circuits.VirtualCircuitType',
  48. on_delete=models.PROTECT,
  49. related_name='virtual_circuits'
  50. )
  51. status = models.CharField(
  52. verbose_name=_('status'),
  53. max_length=50,
  54. choices=CircuitStatusChoices,
  55. default=CircuitStatusChoices.STATUS_ACTIVE
  56. )
  57. tenant = models.ForeignKey(
  58. to='tenancy.Tenant',
  59. on_delete=models.PROTECT,
  60. related_name='virtual_circuits',
  61. blank=True,
  62. null=True
  63. )
  64. group_assignments = GenericRelation(
  65. to='circuits.CircuitGroupAssignment',
  66. content_type_field='member_type',
  67. object_id_field='member_id',
  68. related_query_name='virtual_circuit'
  69. )
  70. clone_fields = (
  71. 'provider_network', 'provider_account', 'status', 'tenant', 'description',
  72. )
  73. prerequisite_models = (
  74. 'circuits.ProviderNetwork',
  75. 'circuits.VirtualCircuitType',
  76. )
  77. class Meta:
  78. ordering = ['provider_network', 'provider_account', 'cid']
  79. constraints = (
  80. models.UniqueConstraint(
  81. fields=('provider_network', 'cid'),
  82. name='%(app_label)s_%(class)s_unique_provider_network_cid'
  83. ),
  84. models.UniqueConstraint(
  85. fields=('provider_account', 'cid'),
  86. name='%(app_label)s_%(class)s_unique_provideraccount_cid'
  87. ),
  88. )
  89. verbose_name = _('virtual circuit')
  90. verbose_name_plural = _('virtual circuits')
  91. def __str__(self):
  92. return self.cid
  93. def get_status_color(self):
  94. return CircuitStatusChoices.colors.get(self.status)
  95. def clean(self):
  96. super().clean()
  97. if self.provider_account and self.provider_network.provider != self.provider_account.provider:
  98. raise ValidationError({
  99. 'provider_account': "The assigned account must belong to the provider of the assigned network."
  100. })
  101. @property
  102. def provider(self):
  103. return self.provider_network.provider
  104. class VirtualCircuitTermination(
  105. CustomFieldsMixin,
  106. CustomLinksMixin,
  107. ExportTemplatesMixin,
  108. TagsMixin,
  109. ChangeLoggedModel
  110. ):
  111. virtual_circuit = models.ForeignKey(
  112. to='circuits.VirtualCircuit',
  113. on_delete=models.CASCADE,
  114. related_name='terminations'
  115. )
  116. role = models.CharField(
  117. verbose_name=_('role'),
  118. max_length=50,
  119. choices=VirtualCircuitTerminationRoleChoices,
  120. default=VirtualCircuitTerminationRoleChoices.ROLE_PEER
  121. )
  122. interface = models.OneToOneField(
  123. to='dcim.Interface',
  124. on_delete=models.CASCADE,
  125. related_name='virtual_circuit_termination'
  126. )
  127. description = models.CharField(
  128. verbose_name=_('description'),
  129. max_length=200,
  130. blank=True
  131. )
  132. class Meta:
  133. ordering = ['virtual_circuit', 'role', 'pk']
  134. verbose_name = _('virtual circuit termination')
  135. verbose_name_plural = _('virtual circuit terminations')
  136. def __str__(self):
  137. return f'{self.virtual_circuit}: {self.get_role_display()} termination'
  138. def get_absolute_url(self):
  139. return reverse('circuits:virtualcircuittermination', args=[self.pk])
  140. def get_role_color(self):
  141. return VirtualCircuitTerminationRoleChoices.colors.get(self.role)
  142. def to_objectchange(self, action):
  143. objectchange = super().to_objectchange(action)
  144. objectchange.related_object = self.virtual_circuit
  145. return objectchange
  146. @property
  147. def parent_object(self):
  148. return self.virtual_circuit
  149. @cached_property
  150. def peer_terminations(self):
  151. if self.role == VirtualCircuitTerminationRoleChoices.ROLE_PEER:
  152. return self.virtual_circuit.terminations.exclude(pk=self.pk).filter(
  153. role=VirtualCircuitTerminationRoleChoices.ROLE_PEER
  154. )
  155. if self.role == VirtualCircuitTerminationRoleChoices.ROLE_HUB:
  156. return self.virtual_circuit.terminations.filter(
  157. role=VirtualCircuitTerminationRoleChoices.ROLE_SPOKE
  158. )
  159. if self.role == VirtualCircuitTerminationRoleChoices.ROLE_SPOKE:
  160. return self.virtual_circuit.terminations.filter(
  161. role=VirtualCircuitTerminationRoleChoices.ROLE_HUB
  162. )
  163. def clean(self):
  164. super().clean()
  165. if self.interface and not self.interface.is_virtual:
  166. raise ValidationError("Virtual circuits may be terminated only to virtual interfaces.")