| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- from functools import cached_property
- from django.contrib.contenttypes.fields import GenericRelation
- from django.core.exceptions import ValidationError
- from django.db import models
- from django.urls import reverse
- from django.utils.translation import gettext_lazy as _
- from circuits.choices import *
- from netbox.models import ChangeLoggedModel, PrimaryModel
- from netbox.models.features import CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, TagsMixin
- from .base import BaseCircuitType
- __all__ = (
- 'VirtualCircuit',
- 'VirtualCircuitTermination',
- 'VirtualCircuitType',
- )
- class VirtualCircuitType(BaseCircuitType):
- """
- Like physical circuits, virtual circuits can be organized by their functional role. For example, a user might wish
- to categorize virtual circuits by their technological nature or by product name.
- """
- class Meta:
- ordering = ('name',)
- verbose_name = _('virtual circuit type')
- verbose_name_plural = _('virtual circuit types')
- class VirtualCircuit(PrimaryModel):
- """
- A virtual connection between two or more endpoints, delivered across one or more physical circuits.
- """
- cid = models.CharField(
- max_length=100,
- verbose_name=_('circuit ID'),
- help_text=_('Unique circuit ID')
- )
- provider_network = models.ForeignKey(
- to='circuits.ProviderNetwork',
- on_delete=models.PROTECT,
- related_name='virtual_circuits'
- )
- provider_account = models.ForeignKey(
- to='circuits.ProviderAccount',
- on_delete=models.PROTECT,
- related_name='virtual_circuits',
- blank=True,
- null=True
- )
- type = models.ForeignKey(
- to='circuits.VirtualCircuitType',
- on_delete=models.PROTECT,
- related_name='virtual_circuits'
- )
- status = models.CharField(
- verbose_name=_('status'),
- max_length=50,
- choices=CircuitStatusChoices,
- default=CircuitStatusChoices.STATUS_ACTIVE
- )
- tenant = models.ForeignKey(
- to='tenancy.Tenant',
- on_delete=models.PROTECT,
- related_name='virtual_circuits',
- blank=True,
- null=True
- )
- group_assignments = GenericRelation(
- to='circuits.CircuitGroupAssignment',
- content_type_field='member_type',
- object_id_field='member_id',
- related_query_name='virtual_circuit'
- )
- clone_fields = (
- 'provider_network', 'provider_account', 'status', 'tenant', 'description',
- )
- prerequisite_models = (
- 'circuits.ProviderNetwork',
- 'circuits.VirtualCircuitType',
- )
- class Meta:
- ordering = ['provider_network', 'provider_account', 'cid']
- constraints = (
- models.UniqueConstraint(
- fields=('provider_network', 'cid'),
- name='%(app_label)s_%(class)s_unique_provider_network_cid'
- ),
- models.UniqueConstraint(
- fields=('provider_account', 'cid'),
- name='%(app_label)s_%(class)s_unique_provideraccount_cid'
- ),
- )
- verbose_name = _('virtual circuit')
- verbose_name_plural = _('virtual circuits')
- def __str__(self):
- return self.cid
- def get_status_color(self):
- return CircuitStatusChoices.colors.get(self.status)
- def clean(self):
- super().clean()
- if self.provider_account and self.provider_network.provider != self.provider_account.provider:
- raise ValidationError({
- 'provider_account': "The assigned account must belong to the provider of the assigned network."
- })
- @property
- def provider(self):
- return self.provider_network.provider
- class VirtualCircuitTermination(
- CustomFieldsMixin,
- CustomLinksMixin,
- ExportTemplatesMixin,
- TagsMixin,
- ChangeLoggedModel
- ):
- virtual_circuit = models.ForeignKey(
- to='circuits.VirtualCircuit',
- on_delete=models.CASCADE,
- related_name='terminations'
- )
- role = models.CharField(
- verbose_name=_('role'),
- max_length=50,
- choices=VirtualCircuitTerminationRoleChoices,
- default=VirtualCircuitTerminationRoleChoices.ROLE_PEER
- )
- interface = models.OneToOneField(
- to='dcim.Interface',
- on_delete=models.CASCADE,
- related_name='virtual_circuit_termination'
- )
- description = models.CharField(
- verbose_name=_('description'),
- max_length=200,
- blank=True
- )
- class Meta:
- ordering = ['virtual_circuit', 'role', 'pk']
- verbose_name = _('virtual circuit termination')
- verbose_name_plural = _('virtual circuit terminations')
- def __str__(self):
- return f'{self.virtual_circuit}: {self.get_role_display()} termination'
- def get_absolute_url(self):
- return reverse('circuits:virtualcircuittermination', args=[self.pk])
- def get_role_color(self):
- return VirtualCircuitTerminationRoleChoices.colors.get(self.role)
- def to_objectchange(self, action):
- objectchange = super().to_objectchange(action)
- objectchange.related_object = self.virtual_circuit
- return objectchange
- @property
- def parent_object(self):
- return self.virtual_circuit
- @cached_property
- def peer_terminations(self):
- if self.role == VirtualCircuitTerminationRoleChoices.ROLE_PEER:
- return self.virtual_circuit.terminations.exclude(pk=self.pk).filter(
- role=VirtualCircuitTerminationRoleChoices.ROLE_PEER
- )
- if self.role == VirtualCircuitTerminationRoleChoices.ROLE_HUB:
- return self.virtual_circuit.terminations.filter(
- role=VirtualCircuitTerminationRoleChoices.ROLE_SPOKE
- )
- if self.role == VirtualCircuitTerminationRoleChoices.ROLE_SPOKE:
- return self.virtual_circuit.terminations.filter(
- role=VirtualCircuitTerminationRoleChoices.ROLE_HUB
- )
- def clean(self):
- super().clean()
- if self.interface and not self.interface.is_virtual:
- raise ValidationError("Virtual circuits may be terminated only to virtual interfaces.")
|