mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 03:56:53 -06:00
Preliminary work on Cables
This commit is contained in:
parent
3eddeeadc5
commit
ea5121ffe1
@ -282,3 +282,20 @@ CONNECTION_STATUS_CHOICES = [
|
|||||||
[CONNECTION_STATUS_PLANNED, 'Planned'],
|
[CONNECTION_STATUS_PLANNED, 'Planned'],
|
||||||
[CONNECTION_STATUS_CONNECTED, 'Connected'],
|
[CONNECTION_STATUS_CONNECTED, 'Connected'],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Cable endpoint types
|
||||||
|
CABLE_ENDPOINT_TYPES = (
|
||||||
|
'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport',
|
||||||
|
)
|
||||||
|
CABLE_CONNECTION_TYPES = CABLE_ENDPOINT_TYPES + (
|
||||||
|
'frontpanelport', 'rearpanelport',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cable types
|
||||||
|
# TODO: Add more types
|
||||||
|
CABLE_TYPE_COPPER = 1000
|
||||||
|
CABLE_TYPE_FIBER = 2000
|
||||||
|
CABLE_TYPE_CHOICES = (
|
||||||
|
(CABLE_TYPE_COPPER, 'Copper'),
|
||||||
|
(CABLE_TYPE_FIBER, 'Fiber'),
|
||||||
|
)
|
||||||
|
178
netbox/dcim/migrations/0066_cables.py
Normal file
178
netbox/dcim/migrations/0066_cables.py
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
# Generated by Django 2.0.8 on 2018-10-18 19:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import utilities.fields
|
||||||
|
|
||||||
|
|
||||||
|
def console_connections_to_cables(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Copy all existing console connections as Cables
|
||||||
|
"""
|
||||||
|
ConsolePort = apps.get_model('dcim', 'ConsolePort')
|
||||||
|
Cable = apps.get_model('dcim', 'Cable')
|
||||||
|
|
||||||
|
# Load content types
|
||||||
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
|
consoleport_type = ContentType.objects.get(app_label='dcim', model='consoleport')
|
||||||
|
consoleserverport_type = ContentType.objects.get(app_label='dcim', model='consoleserverport')
|
||||||
|
|
||||||
|
# Create a new Cable instance from each console connection
|
||||||
|
for consoleport in ConsolePort.objects.filter(cs_port__isnull=False):
|
||||||
|
c = Cable()
|
||||||
|
# We have to assign GFK fields manually because we're inside a migration.
|
||||||
|
c.endpoint_a_type = consoleport_type
|
||||||
|
c.endpoint_a_id = consoleport.id
|
||||||
|
c.endpoint_b_type = consoleserverport_type
|
||||||
|
c.endpoint_b_id = consoleport.cs_port_id
|
||||||
|
c.connection_status = consoleport.connection_status
|
||||||
|
c.save()
|
||||||
|
|
||||||
|
|
||||||
|
def power_connections_to_cables(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Copy all existing power connections as Cables
|
||||||
|
"""
|
||||||
|
PowerPort = apps.get_model('dcim', 'PowerPort')
|
||||||
|
Cable = apps.get_model('dcim', 'Cable')
|
||||||
|
|
||||||
|
# Load content types
|
||||||
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
|
powerport_type = ContentType.objects.get(app_label='dcim', model='powerport')
|
||||||
|
poweroutlet_type = ContentType.objects.get(app_label='dcim', model='poweroutlet')
|
||||||
|
|
||||||
|
# Create a new Cable instance from each power connection
|
||||||
|
for powerport in PowerPort.objects.filter(power_outlet__isnull=False):
|
||||||
|
c = Cable()
|
||||||
|
# We have to assign GFK fields manually because we're inside a migration.
|
||||||
|
c.endpoint_a_type = powerport_type
|
||||||
|
c.endpoint_a_id = powerport.id
|
||||||
|
c.endpoint_b_type = poweroutlet_type
|
||||||
|
c.endpoint_b_id = powerport.power_outlet_id
|
||||||
|
c.connection_status = powerport.connection_status
|
||||||
|
c.save()
|
||||||
|
|
||||||
|
|
||||||
|
def interface_connections_to_cables(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Copy all InterfaceConnections as Cables
|
||||||
|
"""
|
||||||
|
InterfaceConnection = apps.get_model('dcim', 'InterfaceConnection')
|
||||||
|
Cable = apps.get_model('dcim', 'Cable')
|
||||||
|
|
||||||
|
# Load content types
|
||||||
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
|
interface_type = ContentType.objects.get(app_label='dcim', model='interface')
|
||||||
|
|
||||||
|
# Create a new Cable instance from each InterfaceConnection
|
||||||
|
for conn in InterfaceConnection.objects.all():
|
||||||
|
c = Cable()
|
||||||
|
# We have to assign GFK fields manually because we're inside a migration.
|
||||||
|
c.endpoint_a_type = interface_type
|
||||||
|
c.endpoint_a_id = conn.interface_a_id
|
||||||
|
c.endpoint_b_type = interface_type
|
||||||
|
c.endpoint_b_id = conn.interface_b_id
|
||||||
|
c.connection_status = conn.connection_status
|
||||||
|
c.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('dcim', '0065_patch_panel_ports'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Cable',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||||
|
('created', models.DateField(auto_now_add=True, null=True)),
|
||||||
|
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||||
|
('endpoint_a_id', models.PositiveIntegerField()),
|
||||||
|
('endpoint_b_id', models.PositiveIntegerField()),
|
||||||
|
('type', models.PositiveSmallIntegerField(blank=True, null=True)),
|
||||||
|
('status', models.BooleanField(default=True)),
|
||||||
|
('label', models.CharField(blank=True, max_length=100)),
|
||||||
|
('color', utilities.fields.ColorField(blank=True, max_length=6)),
|
||||||
|
('endpoint_a_type', models.ForeignKey(limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontpanelport', 'rearpanelport')}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')),
|
||||||
|
('endpoint_b_type', models.ForeignKey(limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontpanelport', 'rearpanelport')}, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleport',
|
||||||
|
name='connected_endpoint_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleport',
|
||||||
|
name='connected_endpoint_type',
|
||||||
|
field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleserverport',
|
||||||
|
name='connected_endpoint_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleserverport',
|
||||||
|
name='connected_endpoint_type',
|
||||||
|
field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='consoleserverport',
|
||||||
|
name='connection_status',
|
||||||
|
field=models.NullBooleanField(default=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='connected_endpoint_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='connected_endpoint_type',
|
||||||
|
field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='connection_status',
|
||||||
|
field=models.NullBooleanField(default=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poweroutlet',
|
||||||
|
name='connected_endpoint_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poweroutlet',
|
||||||
|
name='connected_endpoint_type',
|
||||||
|
field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poweroutlet',
|
||||||
|
name='connection_status',
|
||||||
|
field=models.NullBooleanField(default=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='powerport',
|
||||||
|
name='connected_endpoint_id',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='powerport',
|
||||||
|
name='connected_endpoint_type',
|
||||||
|
field=models.ForeignKey(blank=True, limit_choices_to={'model__in': ('consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport')}, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='cable',
|
||||||
|
unique_together={('endpoint_b_type', 'endpoint_b_id'), ('endpoint_a_type', 'endpoint_a_id')},
|
||||||
|
),
|
||||||
|
|
||||||
|
# Copy console/power/interface connections as Cables
|
||||||
|
migrations.RunPython(console_connections_to_cables),
|
||||||
|
migrations.RunPython(power_connections_to_cables),
|
||||||
|
migrations.RunPython(interface_connections_to_cables),
|
||||||
|
|
||||||
|
]
|
@ -3,7 +3,8 @@ from itertools import count, groupby
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.fields import GenericRelation
|
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.postgres.fields import ArrayField, JSONField
|
from django.contrib.postgres.fields import ArrayField, JSONField
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
@ -65,6 +66,32 @@ class ComponentModel(models.Model):
|
|||||||
).save()
|
).save()
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectableModel(models.Model):
|
||||||
|
connected_endpoint_type = models.ForeignKey(
|
||||||
|
to=ContentType,
|
||||||
|
limit_choices_to={'model__in': CABLE_ENDPOINT_TYPES},
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='+',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
connected_endpoint_id = models.PositiveIntegerField(
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
connected_endpoint = GenericForeignKey(
|
||||||
|
ct_field='connected_endpoint_type',
|
||||||
|
fk_field='connected_endpoint_id'
|
||||||
|
)
|
||||||
|
connection_status = models.NullBooleanField(
|
||||||
|
choices=CONNECTION_STATUS_CHOICES,
|
||||||
|
default=CONNECTION_STATUS_CONNECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Regions
|
# Regions
|
||||||
#
|
#
|
||||||
@ -1616,7 +1643,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
# Console ports
|
# Console ports
|
||||||
#
|
#
|
||||||
|
|
||||||
class ConsolePort(ComponentModel):
|
class ConsolePort(ConnectableModel, ComponentModel):
|
||||||
"""
|
"""
|
||||||
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
|
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
|
||||||
"""
|
"""
|
||||||
@ -1679,7 +1706,7 @@ class ConsoleServerPortManager(models.Manager):
|
|||||||
}).order_by('device', 'name_padded')
|
}).order_by('device', 'name_padded')
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPort(ComponentModel):
|
class ConsoleServerPort(ConnectableModel, ComponentModel):
|
||||||
"""
|
"""
|
||||||
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
|
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
|
||||||
"""
|
"""
|
||||||
@ -1720,7 +1747,7 @@ class ConsoleServerPort(ComponentModel):
|
|||||||
# Power ports
|
# Power ports
|
||||||
#
|
#
|
||||||
|
|
||||||
class PowerPort(ComponentModel):
|
class PowerPort(ConnectableModel, ComponentModel):
|
||||||
"""
|
"""
|
||||||
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
|
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
|
||||||
"""
|
"""
|
||||||
@ -1782,7 +1809,7 @@ class PowerOutletManager(models.Manager):
|
|||||||
}).order_by('device', 'name_padded')
|
}).order_by('device', 'name_padded')
|
||||||
|
|
||||||
|
|
||||||
class PowerOutlet(ComponentModel):
|
class PowerOutlet(ConnectableModel, ComponentModel):
|
||||||
"""
|
"""
|
||||||
A physical power outlet (output) within a Device which provides power to a PowerPort.
|
A physical power outlet (output) within a Device which provides power to a PowerPort.
|
||||||
"""
|
"""
|
||||||
@ -1823,7 +1850,7 @@ class PowerOutlet(ComponentModel):
|
|||||||
# Interfaces
|
# Interfaces
|
||||||
#
|
#
|
||||||
|
|
||||||
class Interface(ComponentModel):
|
class Interface(ConnectableModel, ComponentModel):
|
||||||
"""
|
"""
|
||||||
A network interface within a Device or VirtualMachine. A physical Interface can connect to exactly one other
|
A network interface within a Device or VirtualMachine. A physical Interface can connect to exactly one other
|
||||||
Interface via the creation of an InterfaceConnection.
|
Interface via the creation of an InterfaceConnection.
|
||||||
@ -2423,3 +2450,64 @@ class VirtualChassis(ChangeLoggedModel):
|
|||||||
self.master,
|
self.master,
|
||||||
self.domain,
|
self.domain,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Cables
|
||||||
|
#
|
||||||
|
|
||||||
|
class Cable(ChangeLoggedModel):
|
||||||
|
"""
|
||||||
|
A physical connection between two endpoints.
|
||||||
|
"""
|
||||||
|
endpoint_a_type = models.ForeignKey(
|
||||||
|
to=ContentType,
|
||||||
|
limit_choices_to={'model__in': CABLE_CONNECTION_TYPES},
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='+'
|
||||||
|
)
|
||||||
|
endpoint_a_id = models.PositiveIntegerField()
|
||||||
|
endpoint_a = GenericForeignKey(
|
||||||
|
ct_field='endpoint_a_type',
|
||||||
|
fk_field='endpoint_a_id'
|
||||||
|
)
|
||||||
|
endpoint_b_type = models.ForeignKey(
|
||||||
|
to=ContentType,
|
||||||
|
limit_choices_to={'model__in': CABLE_CONNECTION_TYPES},
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='+'
|
||||||
|
)
|
||||||
|
endpoint_b_id = models.PositiveIntegerField()
|
||||||
|
endpoint_b = GenericForeignKey(
|
||||||
|
ct_field='endpoint_b_type',
|
||||||
|
fk_field='endpoint_b_id'
|
||||||
|
)
|
||||||
|
type = models.PositiveSmallIntegerField(
|
||||||
|
choices=CABLE_TYPE_CHOICES,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
status = models.BooleanField(
|
||||||
|
choices=CONNECTION_STATUS_CHOICES,
|
||||||
|
default=CONNECTION_STATUS_CONNECTED
|
||||||
|
)
|
||||||
|
label = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
color = ColorField(
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = (
|
||||||
|
('endpoint_a_type', 'endpoint_a_id'),
|
||||||
|
('endpoint_b_type', 'endpoint_b_id'),
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: This should follow all cables in a path
|
||||||
|
def get_path_endpoints(self):
|
||||||
|
"""
|
||||||
|
Return the endpoints connected by this cable path.
|
||||||
|
"""
|
||||||
|
return (self.endpoint_a, self.endpoint_b)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django.db.models.signals import post_save, pre_delete
|
from django.db.models.signals import post_save, post_delete, pre_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from .models import Device, VirtualChassis
|
from .models import Cable, Device, VirtualChassis
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=VirtualChassis)
|
@receiver(post_save, sender=VirtualChassis)
|
||||||
@ -19,3 +19,27 @@ def clear_virtualchassis_members(instance, **kwargs):
|
|||||||
When a VirtualChassis is deleted, nullify the vc_position and vc_priority fields of its prior members.
|
When a VirtualChassis is deleted, nullify the vc_position and vc_priority fields of its prior members.
|
||||||
"""
|
"""
|
||||||
Device.objects.filter(virtual_chassis=instance.pk).update(vc_position=None, vc_priority=None)
|
Device.objects.filter(virtual_chassis=instance.pk).update(vc_position=None, vc_priority=None)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=Cable)
|
||||||
|
def update_connected_endpoints(instance, **kwargs):
|
||||||
|
"""
|
||||||
|
When a Cable is saved, update its connected endpoints.
|
||||||
|
"""
|
||||||
|
endpoint_a, endpoint_b = instance.get_path_endpoints()
|
||||||
|
endpoint_a.connected_endpoint = endpoint_b
|
||||||
|
endpoint_a.save()
|
||||||
|
endpoint_b.connected_endpoint = endpoint_a
|
||||||
|
endpoint_b.save()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_delete, sender=Cable)
|
||||||
|
def nullify_connected_endpoints(instance, **kwargs):
|
||||||
|
"""
|
||||||
|
When a Cable is deleted, nullify its connected endpoints.
|
||||||
|
"""
|
||||||
|
endpoint_a, endpoint_b = instance.get_path_endpoints()
|
||||||
|
endpoint_a.connected_endpoint = None
|
||||||
|
endpoint_a.save()
|
||||||
|
endpoint_b.connected_endpoint = None
|
||||||
|
endpoint_b.save()
|
||||||
|
Loading…
Reference in New Issue
Block a user