1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00
2021-05-21 16:54:33 -04:00

393 lines
10 KiB
Python

from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from dcim.fields import ASNField
from dcim.models import CableTermination, PathEndpoint
from extras.models import ObjectChange
from extras.utils import extras_features
from netbox.models import BigIDModel, ChangeLoggedModel, OrganizationalModel, PrimaryModel
from utilities.querysets import RestrictedQuerySet
from .choices import *
__all__ = (
'Circuit',
'CircuitTermination',
'CircuitType',
'ProviderNetwork',
'Provider',
)
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class Provider(PrimaryModel):
"""
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
stores information pertinent to the user's relationship with the Provider.
"""
name = models.CharField(
max_length=100,
unique=True
)
slug = models.SlugField(
max_length=100,
unique=True
)
asn = ASNField(
blank=True,
null=True,
verbose_name='ASN',
help_text='32-bit autonomous system number'
)
account = models.CharField(
max_length=30,
blank=True,
verbose_name='Account number'
)
portal_url = models.URLField(
blank=True,
verbose_name='Portal URL'
)
noc_contact = models.TextField(
blank=True,
verbose_name='NOC contact'
)
admin_contact = models.TextField(
blank=True,
verbose_name='Admin contact'
)
comments = models.TextField(
blank=True
)
objects = RestrictedQuerySet.as_manager()
csv_headers = [
'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
]
clone_fields = [
'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact',
]
class Meta:
ordering = ['name']
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('circuits:provider', args=[self.pk])
def to_csv(self):
return (
self.name,
self.slug,
self.asn,
self.account,
self.portal_url,
self.noc_contact,
self.admin_contact,
self.comments,
)
#
# Provider networks
#
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class ProviderNetwork(PrimaryModel):
"""
This represents a provider network which exists outside of NetBox, the details of which are unknown or
unimportant to the user.
"""
name = models.CharField(
max_length=100
)
provider = models.ForeignKey(
to='circuits.Provider',
on_delete=models.PROTECT,
related_name='networks'
)
description = models.CharField(
max_length=200,
blank=True
)
comments = models.TextField(
blank=True
)
csv_headers = [
'provider', 'name', 'description', 'comments',
]
objects = RestrictedQuerySet.as_manager()
class Meta:
ordering = ('provider', 'name')
constraints = (
models.UniqueConstraint(
fields=('provider', 'name'),
name='circuits_providernetwork_provider_name'
),
)
unique_together = ('provider', 'name')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('circuits:providernetwork', args=[self.pk])
def to_csv(self):
return (
self.provider.name,
self.name,
self.description,
self.comments,
)
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
class CircuitType(OrganizationalModel):
"""
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
"Long Haul," "Metro," or "Out-of-Band".
"""
name = models.CharField(
max_length=100,
unique=True
)
slug = models.SlugField(
max_length=100,
unique=True
)
description = models.CharField(
max_length=200,
blank=True,
)
objects = RestrictedQuerySet.as_manager()
csv_headers = ['name', 'slug', 'description']
class Meta:
ordering = ['name']
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('circuits:circuittype', args=[self.pk])
def to_csv(self):
return (
self.name,
self.slug,
self.description,
)
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class Circuit(PrimaryModel):
"""
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured
in Kbps.
"""
cid = models.CharField(
max_length=100,
verbose_name='Circuit ID'
)
provider = models.ForeignKey(
to='circuits.Provider',
on_delete=models.PROTECT,
related_name='circuits'
)
type = models.ForeignKey(
to='CircuitType',
on_delete=models.PROTECT,
related_name='circuits'
)
status = models.CharField(
max_length=50,
choices=CircuitStatusChoices,
default=CircuitStatusChoices.STATUS_ACTIVE
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
on_delete=models.PROTECT,
related_name='circuits',
blank=True,
null=True
)
install_date = models.DateField(
blank=True,
null=True,
verbose_name='Date installed'
)
commit_rate = models.PositiveIntegerField(
blank=True,
null=True,
verbose_name='Commit rate (Kbps)')
description = models.CharField(
max_length=200,
blank=True
)
comments = models.TextField(
blank=True
)
# Cache associated CircuitTerminations
termination_a = models.ForeignKey(
to='circuits.CircuitTermination',
on_delete=models.SET_NULL,
related_name='+',
editable=False,
blank=True,
null=True
)
termination_z = models.ForeignKey(
to='circuits.CircuitTermination',
on_delete=models.SET_NULL,
related_name='+',
editable=False,
blank=True,
null=True
)
objects = RestrictedQuerySet.as_manager()
csv_headers = [
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
]
clone_fields = [
'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description',
]
class Meta:
ordering = ['provider', 'cid']
unique_together = ['provider', 'cid']
def __str__(self):
return self.cid
def get_absolute_url(self):
return reverse('circuits:circuit', args=[self.pk])
def to_csv(self):
return (
self.cid,
self.provider.name,
self.type.name,
self.get_status_display(),
self.tenant.name if self.tenant else None,
self.install_date,
self.commit_rate,
self.description,
self.comments,
)
def get_status_class(self):
return CircuitStatusChoices.CSS_CLASSES.get(self.status)
@extras_features('webhooks')
class CircuitTermination(ChangeLoggedModel, CableTermination):
circuit = models.ForeignKey(
to='circuits.Circuit',
on_delete=models.CASCADE,
related_name='terminations'
)
term_side = models.CharField(
max_length=1,
choices=CircuitTerminationSideChoices,
verbose_name='Termination'
)
site = models.ForeignKey(
to='dcim.Site',
on_delete=models.PROTECT,
related_name='circuit_terminations',
blank=True,
null=True
)
provider_network = models.ForeignKey(
to=ProviderNetwork,
on_delete=models.PROTECT,
related_name='circuit_terminations',
blank=True,
null=True
)
port_speed = models.PositiveIntegerField(
verbose_name='Port speed (Kbps)',
blank=True,
null=True
)
upstream_speed = models.PositiveIntegerField(
blank=True,
null=True,
verbose_name='Upstream speed (Kbps)',
help_text='Upstream speed, if different from port speed'
)
xconnect_id = models.CharField(
max_length=50,
blank=True,
verbose_name='Cross-connect ID'
)
pp_info = models.CharField(
max_length=100,
blank=True,
verbose_name='Patch panel/port(s)'
)
description = models.CharField(
max_length=200,
blank=True
)
objects = RestrictedQuerySet.as_manager()
class Meta:
ordering = ['circuit', 'term_side']
unique_together = ['circuit', 'term_side']
def __str__(self):
return f'Termination {self.term_side}: {self.site or self.provider_network}'
def get_absolute_url(self):
if self.site:
return self.site.get_absolute_url()
return self.provider_network.get_absolute_url()
def clean(self):
super().clean()
# Must define either site *or* provider network
if self.site is None and self.provider_network is None:
raise ValidationError("A circuit termination must attach to either a site or a provider network.")
if self.site and self.provider_network:
raise ValidationError("A circuit termination cannot attach to both a site and a provider network.")
def to_objectchange(self, action):
# Annotate the parent Circuit
try:
circuit = self.circuit
except Circuit.DoesNotExist:
# Parent circuit has been deleted
circuit = None
return super().to_objectchange(action, related_object=circuit)
@property
def parent_object(self):
return self.circuit
def get_peer_termination(self):
peer_side = 'Z' if self.term_side == 'A' else 'A'
try:
return CircuitTermination.objects.prefetch_related('site').get(
circuit=self.circuit,
term_side=peer_side
)
except CircuitTermination.DoesNotExist:
return None