|
|
@@ -19,7 +19,6 @@ from utilities.ordering import naturalize_interface
|
|
|
from utilities.querysets import RestrictedQuerySet
|
|
|
from utilities.query_functions import CollateAsChar
|
|
|
from utilities.utils import serialize_object
|
|
|
-from virtualization.choices import VMInterfaceTypeChoices
|
|
|
|
|
|
|
|
|
__all__ = (
|
|
|
@@ -53,18 +52,12 @@ class ComponentModel(models.Model):
|
|
|
return self.name
|
|
|
|
|
|
def to_objectchange(self, action):
|
|
|
- # Annotate the parent Device/VM
|
|
|
- try:
|
|
|
- parent = getattr(self, 'device', None) or getattr(self, 'virtual_machine', None)
|
|
|
- except ObjectDoesNotExist:
|
|
|
- # The parent device/VM has already been deleted
|
|
|
- parent = None
|
|
|
-
|
|
|
+ # Annotate the parent Device
|
|
|
return ObjectChange(
|
|
|
changed_object=self,
|
|
|
object_repr=str(self),
|
|
|
action=action,
|
|
|
- related_object=parent,
|
|
|
+ related_object=self.device,
|
|
|
object_data=serialize_object(self)
|
|
|
)
|
|
|
|
|
|
@@ -592,10 +585,44 @@ class PowerOutlet(CableTermination, ComponentModel):
|
|
|
# Interfaces
|
|
|
#
|
|
|
|
|
|
+class BaseInterface(models.Model):
|
|
|
+ name = models.CharField(
|
|
|
+ max_length=64
|
|
|
+ )
|
|
|
+ _name = NaturalOrderingField(
|
|
|
+ target_field='name',
|
|
|
+ naturalize_function=naturalize_interface,
|
|
|
+ max_length=100,
|
|
|
+ blank=True
|
|
|
+ )
|
|
|
+ enabled = models.BooleanField(
|
|
|
+ default=True
|
|
|
+ )
|
|
|
+ mac_address = MACAddressField(
|
|
|
+ null=True,
|
|
|
+ blank=True,
|
|
|
+ verbose_name='MAC Address'
|
|
|
+ )
|
|
|
+ mtu = models.PositiveIntegerField(
|
|
|
+ blank=True,
|
|
|
+ null=True,
|
|
|
+ validators=[MinValueValidator(1), MaxValueValidator(65536)],
|
|
|
+ verbose_name='MTU'
|
|
|
+ )
|
|
|
+ mode = models.CharField(
|
|
|
+ max_length=50,
|
|
|
+ choices=InterfaceModeChoices,
|
|
|
+ blank=True
|
|
|
+ )
|
|
|
+
|
|
|
+ class Meta:
|
|
|
+ abstract = True
|
|
|
+
|
|
|
+
|
|
|
@extras_features('graphs', 'export_templates', 'webhooks')
|
|
|
-class Interface(CableTermination, ComponentModel):
|
|
|
+class Interface(CableTermination, ComponentModel, BaseInterface):
|
|
|
"""
|
|
|
- A network interface within a Device or VirtualMachine. A physical Interface can connect to exactly one other
|
|
|
+ A network interface within a Device. A physical Interface can connect to exactly one other
|
|
|
Interface.
|
|
|
"""
|
|
|
device = models.ForeignKey(
|
|
|
@@ -605,22 +632,6 @@ class Interface(CableTermination, ComponentModel):
|
|
|
null=True,
|
|
|
blank=True
|
|
|
)
|
|
|
- virtual_machine = models.ForeignKey(
|
|
|
- to='virtualization.VirtualMachine',
|
|
|
- on_delete=models.CASCADE,
|
|
|
- related_name='interfaces',
|
|
|
- null=True,
|
|
|
- blank=True
|
|
|
- )
|
|
|
- name = models.CharField(
|
|
|
- max_length=64
|
|
|
- )
|
|
|
- _name = NaturalOrderingField(
|
|
|
- target_field='name',
|
|
|
- naturalize_function=naturalize_interface,
|
|
|
- max_length=100,
|
|
|
- blank=True
|
|
|
- )
|
|
|
label = models.CharField(
|
|
|
max_length=64,
|
|
|
blank=True,
|
|
|
@@ -656,30 +667,11 @@ class Interface(CableTermination, ComponentModel):
|
|
|
max_length=50,
|
|
|
choices=InterfaceTypeChoices
|
|
|
)
|
|
|
- enabled = models.BooleanField(
|
|
|
- default=True
|
|
|
- )
|
|
|
- mac_address = MACAddressField(
|
|
|
- null=True,
|
|
|
- blank=True,
|
|
|
- verbose_name='MAC Address'
|
|
|
- )
|
|
|
- mtu = models.PositiveIntegerField(
|
|
|
- blank=True,
|
|
|
- null=True,
|
|
|
- validators=[MinValueValidator(1), MaxValueValidator(65536)],
|
|
|
- verbose_name='MTU'
|
|
|
- )
|
|
|
mgmt_only = models.BooleanField(
|
|
|
default=False,
|
|
|
verbose_name='OOB Management',
|
|
|
help_text='This interface is used only for out-of-band management'
|
|
|
)
|
|
|
- mode = models.CharField(
|
|
|
- max_length=50,
|
|
|
- choices=InterfaceModeChoices,
|
|
|
- blank=True
|
|
|
- )
|
|
|
untagged_vlan = models.ForeignKey(
|
|
|
to='ipam.VLAN',
|
|
|
on_delete=models.SET_NULL,
|
|
|
@@ -694,15 +686,19 @@ class Interface(CableTermination, ComponentModel):
|
|
|
blank=True,
|
|
|
verbose_name='Tagged VLANs'
|
|
|
)
|
|
|
+ ipaddresses = GenericRelation(
|
|
|
+ to='ipam.IPAddress',
|
|
|
+ content_type_field='assigned_object_type',
|
|
|
+ object_id_field='assigned_object_id'
|
|
|
+ )
|
|
|
tags = TaggableManager(through=TaggedItem)
|
|
|
|
|
|
csv_headers = [
|
|
|
- 'device', 'virtual_machine', 'name', 'lag', 'type', 'enabled', 'mac_address', 'mtu', 'mgmt_only',
|
|
|
+ 'device', 'name', 'lag', 'type', 'enabled', 'mac_address', 'mtu', 'mgmt_only',
|
|
|
'description', 'mode',
|
|
|
]
|
|
|
|
|
|
class Meta:
|
|
|
- # TODO: ordering and unique_together should include virtual_machine
|
|
|
ordering = ('device', CollateAsChar('_name'))
|
|
|
unique_together = ('device', 'name')
|
|
|
|
|
|
@@ -712,7 +708,6 @@ class Interface(CableTermination, ComponentModel):
|
|
|
def to_csv(self):
|
|
|
return (
|
|
|
self.device.identifier if self.device else None,
|
|
|
- self.virtual_machine.name if self.virtual_machine else None,
|
|
|
self.name,
|
|
|
self.lag.name if self.lag else None,
|
|
|
self.get_type_display(),
|
|
|
@@ -726,18 +721,6 @@ class Interface(CableTermination, ComponentModel):
|
|
|
|
|
|
def clean(self):
|
|
|
|
|
|
- # An Interface must belong to a Device *or* to a VirtualMachine
|
|
|
- if self.device and self.virtual_machine:
|
|
|
- raise ValidationError("An interface cannot belong to both a device and a virtual machine.")
|
|
|
- if not self.device and not self.virtual_machine:
|
|
|
- raise ValidationError("An interface must belong to either a device or a virtual machine.")
|
|
|
-
|
|
|
- # VM interfaces must be virtual
|
|
|
- if self.virtual_machine and self.type not in VMInterfaceTypeChoices.values():
|
|
|
- raise ValidationError({
|
|
|
- 'type': "Invalid interface type for a virtual machine: {}".format(self.type)
|
|
|
- })
|
|
|
-
|
|
|
# Virtual interfaces cannot be connected
|
|
|
if self.type in NONCONNECTABLE_IFACE_TYPES and (
|
|
|
self.cable or getattr(self, 'circuit_termination', False)
|
|
|
@@ -773,7 +756,7 @@ class Interface(CableTermination, ComponentModel):
|
|
|
if self.untagged_vlan and self.untagged_vlan.site not in [self.parent.site, None]:
|
|
|
raise ValidationError({
|
|
|
'untagged_vlan': "The untagged VLAN ({}) must belong to the same site as the interface's parent "
|
|
|
- "device/VM, or it must be global".format(self.untagged_vlan)
|
|
|
+ "device, or it must be global".format(self.untagged_vlan)
|
|
|
})
|
|
|
|
|
|
def save(self, *args, **kwargs):
|
|
|
@@ -788,21 +771,6 @@ class Interface(CableTermination, ComponentModel):
|
|
|
|
|
|
return super().save(*args, **kwargs)
|
|
|
|
|
|
- def to_objectchange(self, action):
|
|
|
- # Annotate the parent Device/VM
|
|
|
- try:
|
|
|
- parent_obj = self.device or self.virtual_machine
|
|
|
- except ObjectDoesNotExist:
|
|
|
- parent_obj = None
|
|
|
-
|
|
|
- return ObjectChange(
|
|
|
- changed_object=self,
|
|
|
- object_repr=str(self),
|
|
|
- action=action,
|
|
|
- related_object=parent_obj,
|
|
|
- object_data=serialize_object(self)
|
|
|
- )
|
|
|
-
|
|
|
@property
|
|
|
def connected_endpoint(self):
|
|
|
"""
|
|
|
@@ -841,7 +809,7 @@ class Interface(CableTermination, ComponentModel):
|
|
|
|
|
|
@property
|
|
|
def parent(self):
|
|
|
- return self.device or self.virtual_machine
|
|
|
+ return self.device
|
|
|
|
|
|
@property
|
|
|
def is_connectable(self):
|