From d59f0891e4d6c0b7ebca57b958d93d63329b367f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 6 Oct 2020 12:10:12 -0400 Subject: [PATCH] Cache peer termination on CableTerminations --- .../migrations/0022_cache_cable_peer.py | 49 ++++++ .../dcim/migrations/0121_cache_cable_peer.py | 141 ++++++++++++++++++ netbox/dcim/models/device_components.py | 25 +++- netbox/dcim/signals.py | 4 + netbox/dcim/tests/test_models.py | 6 +- 5 files changed, 217 insertions(+), 8 deletions(-) create mode 100644 netbox/circuits/migrations/0022_cache_cable_peer.py create mode 100644 netbox/dcim/migrations/0121_cache_cable_peer.py diff --git a/netbox/circuits/migrations/0022_cache_cable_peer.py b/netbox/circuits/migrations/0022_cache_cable_peer.py new file mode 100644 index 000000000..9a470a3c2 --- /dev/null +++ b/netbox/circuits/migrations/0022_cache_cable_peer.py @@ -0,0 +1,49 @@ +import sys + +from django.db import migrations, models +import django.db.models.deletion + + +def cache_cable_peers(apps, schema_editor): + ContentType = apps.get_model('contenttypes', 'ContentType') + Cable = apps.get_model('dcim', 'Cable') + CircuitTermination = apps.get_model('circuits', 'CircuitTermination') + + if 'test' not in sys.argv: + print(f"\n Updating circuit termination cable peers...", flush=True) + ct = ContentType.objects.get_for_model(CircuitTermination) + for cable in Cable.objects.filter(termination_a_type=ct): + CircuitTermination.objects.filter(pk=cable.termination_a_id).update( + _cable_peer_type_id=cable.termination_b_type_id, + _cable_peer_id=cable.termination_b_id + ) + for cable in Cable.objects.filter(termination_b_type=ct): + CircuitTermination.objects.filter(pk=cable.termination_b_id).update( + _cable_peer_type_id=cable.termination_a_type_id, + _cable_peer_id=cable.termination_a_id + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('circuits', '0021_cablepath'), + ] + + operations = [ + migrations.AddField( + model_name='circuittermination', + name='_cable_peer_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='circuittermination', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.RunPython( + code=cache_cable_peers, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/dcim/migrations/0121_cache_cable_peer.py b/netbox/dcim/migrations/0121_cache_cable_peer.py new file mode 100644 index 000000000..aeb89c5d3 --- /dev/null +++ b/netbox/dcim/migrations/0121_cache_cable_peer.py @@ -0,0 +1,141 @@ +import sys + +from django.db import migrations, models +import django.db.models.deletion + + +def cache_cable_peers(apps, schema_editor): + ContentType = apps.get_model('contenttypes', 'ContentType') + Cable = apps.get_model('dcim', 'Cable') + ConsolePort = apps.get_model('dcim', 'ConsolePort') + ConsoleServerPort = apps.get_model('dcim', 'ConsoleServerPort') + PowerPort = apps.get_model('dcim', 'PowerPort') + PowerOutlet = apps.get_model('dcim', 'PowerOutlet') + Interface = apps.get_model('dcim', 'Interface') + FrontPort = apps.get_model('dcim', 'FrontPort') + RearPort = apps.get_model('dcim', 'RearPort') + PowerFeed = apps.get_model('dcim', 'PowerFeed') + + models = ( + ConsolePort, + ConsoleServerPort, + PowerPort, + PowerOutlet, + Interface, + FrontPort, + RearPort, + PowerFeed + ) + + if 'test' not in sys.argv: + print("\n", end="") + + for model in models: + if 'test' not in sys.argv: + print(f" Updating {model._meta.verbose_name} cable peers...", flush=True) + ct = ContentType.objects.get_for_model(model) + for cable in Cable.objects.filter(termination_a_type=ct): + model.objects.filter(pk=cable.termination_a_id).update( + _cable_peer_type_id=cable.termination_b_type_id, + _cable_peer_id=cable.termination_b_id + ) + for cable in Cable.objects.filter(termination_b_type=ct): + model.objects.filter(pk=cable.termination_b_id).update( + _cable_peer_type_id=cable.termination_a_type_id, + _cable_peer_id=cable.termination_a_id + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('dcim', '0120_cablepath'), + ] + + operations = [ + migrations.AddField( + model_name='consoleport', + name='_cable_peer_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='consoleport', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='consoleserverport', + name='_cable_peer_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='consoleserverport', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='frontport', + name='_cable_peer_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='frontport', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='interface', + name='_cable_peer_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='interface', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='powerfeed', + name='_cable_peer_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='powerfeed', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='poweroutlet', + name='_cable_peer_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='poweroutlet', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='powerport', + name='_cable_peer_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='powerport', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='rearport', + name='_cable_peer_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='rearport', + name='_cable_peer_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'), + ), + migrations.RunPython( + code=cache_cable_peers, + reverse_code=migrations.RunPython.noop + ), + ] diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 2ab3ce7c8..1bd577cdf 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -1,4 +1,5 @@ -from django.contrib.contenttypes.fields import GenericRelation +from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models @@ -99,6 +100,21 @@ class CableTermination(models.Model): blank=True, null=True ) + _cable_peer_type = models.ForeignKey( + to=ContentType, + on_delete=models.SET_NULL, + related_name='+', + blank=True, + null=True + ) + _cable_peer_id = models.PositiveIntegerField( + blank=True, + null=True + ) + _cable_peer = GenericForeignKey( + ct_field='_cable_peer_type', + fk_field='_cable_peer_id' + ) # Generic relations to Cable. These ensure that an attached Cable is deleted if the terminated object is deleted. _cabled_as_a = GenericRelation( @@ -116,12 +132,7 @@ class CableTermination(models.Model): abstract = True def get_cable_peer(self): - if self.cable is None: - return None - if self._cabled_as_a.exists(): - return self.cable.termination_b - if self._cabled_as_b.exists(): - return self.cable.termination_a + return self._cable_peer class PathEndpoint(models.Model): diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py index ee006c9d7..5e5915313 100644 --- a/netbox/dcim/signals.py +++ b/netbox/dcim/signals.py @@ -67,10 +67,12 @@ def update_connected_endpoints(instance, created, **kwargs): if instance.termination_a.cable != instance: logger.debug(f"Updating termination A for cable {instance}") instance.termination_a.cable = instance + instance.termination_a._cable_peer = instance.termination_b instance.termination_a.save() if instance.termination_b.cable != instance: logger.debug(f"Updating termination B for cable {instance}") instance.termination_b.cable = instance + instance.termination_b._cable_peer = instance.termination_a instance.termination_b.save() # Create/update cable paths @@ -101,10 +103,12 @@ def nullify_connected_endpoints(instance, **kwargs): if instance.termination_a is not None: logger.debug(f"Nullifying termination A for cable {instance}") instance.termination_a.cable = None + instance.termination_a._cable_peer = None instance.termination_a.save() if instance.termination_b is not None: logger.debug(f"Nullifying termination B for cable {instance}") instance.termination_b.cable = None + instance.termination_b._cable_peer = None instance.termination_b.save() # Delete and retrace any dependent cable paths diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index 83438a609..01829d7bc 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -398,9 +398,11 @@ class CableTestCase(TestCase): When a new Cable is created, it must be cached on either termination point. """ interface1 = Interface.objects.get(pk=self.interface1.pk) - self.assertEqual(self.cable.termination_a, interface1) interface2 = Interface.objects.get(pk=self.interface2.pk) + self.assertEqual(self.cable.termination_a, interface1) + self.assertEqual(interface1._cable_peer, interface2) self.assertEqual(self.cable.termination_b, interface2) + self.assertEqual(interface2._cable_peer, interface1) def test_cable_deletion(self): """ @@ -412,8 +414,10 @@ class CableTestCase(TestCase): self.assertNotEqual(str(self.cable), '#None') interface1 = Interface.objects.get(pk=self.interface1.pk) self.assertIsNone(interface1.cable) + self.assertIsNone(interface1._cable_peer) interface2 = Interface.objects.get(pk=self.interface2.pk) self.assertIsNone(interface2.cable) + self.assertIsNone(interface2._cable_peer) def test_cabletermination_deletion(self): """