|
|
@@ -1,3 +1,7 @@
|
|
|
+from io import StringIO
|
|
|
+
|
|
|
+from django.core.management import call_command
|
|
|
+
|
|
|
from circuits.models import *
|
|
|
from dcim.choices import LinkStatusChoices
|
|
|
from dcim.models import *
|
|
|
@@ -2891,6 +2895,93 @@ class LegacyCablePathTestCase(BaseCablePathTestCase):
|
|
|
# Verify _path is cleared on removed interface (#21127)
|
|
|
interface3.refresh_from_db()
|
|
|
self.assertPathIsNotSet(interface3)
|
|
|
+ self.assertEqual(CablePath.objects.count(), 2)
|
|
|
+
|
|
|
+ def test_304_replacing_a_termination_retires_superseded_paths(self):
|
|
|
+ """
|
|
|
+ [IF1] --C1-- [IF2] becomes [IF1] --C1-- [IF3], and back again
|
|
|
+
|
|
|
+ Each replacement must leave only the two paths the cable's current terminations trace.
|
|
|
+ """
|
|
|
+ interface1 = Interface.objects.create(device=self.device, name='Interface 1')
|
|
|
+ interface2 = Interface.objects.create(device=self.device, name='Interface 2')
|
|
|
+ interface3 = Interface.objects.create(device=self.device, name='Interface 3')
|
|
|
+
|
|
|
+ cable1 = Cable(a_terminations=[interface1], b_terminations=[interface2])
|
|
|
+ cable1.save()
|
|
|
+ self.assertEqual(CablePath.objects.count(), 2)
|
|
|
+
|
|
|
+ for peer, detached in ((interface3, interface2), (interface2, interface3)):
|
|
|
+ with self.subTest(peer=peer.name):
|
|
|
+ cable1 = Cable.objects.get(pk=cable1.pk)
|
|
|
+ cable1.b_terminations = [peer]
|
|
|
+ cable1.full_clean()
|
|
|
+ cable1.save()
|
|
|
+
|
|
|
+ self.assertCurrentPathExists((interface1, cable1, peer), is_complete=True, is_active=True)
|
|
|
+ self.assertCurrentPathExists((peer, cable1, interface1), is_complete=True, is_active=True)
|
|
|
+ self.assertEqual(CablePath.objects.count(), 2)
|
|
|
+ detached.refresh_from_db()
|
|
|
+ self.assertIsNone(detached.cable)
|
|
|
+ self.assertPathIsNotSet(detached)
|
|
|
+
|
|
|
+ def test_305_adding_a_termination_retires_superseded_paths(self):
|
|
|
+ """
|
|
|
+ [IF1] --C1-- [IF2] gains a second B-side termination [IF3]
|
|
|
+
|
|
|
+ Extending an end must retire the paths whose destinations the extension supersedes.
|
|
|
+ """
|
|
|
+ interface1 = Interface.objects.create(device=self.device, name='Interface 1')
|
|
|
+ interface2 = Interface.objects.create(device=self.device, name='Interface 2')
|
|
|
+ interface3 = Interface.objects.create(device=self.device, name='Interface 3')
|
|
|
+
|
|
|
+ cable1 = Cable(a_terminations=[interface1], b_terminations=[interface2])
|
|
|
+ cable1.save()
|
|
|
+ self.assertEqual(CablePath.objects.count(), 2)
|
|
|
+
|
|
|
+ cable1 = Cable.objects.get(pk=cable1.pk)
|
|
|
+ cable1.b_terminations = [interface2, interface3]
|
|
|
+ cable1.full_clean()
|
|
|
+ cable1.save()
|
|
|
+
|
|
|
+ self.assertCurrentPathExists(
|
|
|
+ (interface1, cable1, [interface2, interface3]), is_complete=True, is_active=True
|
|
|
+ )
|
|
|
+ path2 = self.assertPathExists(
|
|
|
+ ([interface2, interface3], cable1, interface1), is_complete=True, is_active=True
|
|
|
+ )
|
|
|
+ for interface in (interface2, interface3):
|
|
|
+ interface.refresh_from_db()
|
|
|
+ self.assertPathIsSet(interface, path2)
|
|
|
+ self.assertEqual(CablePath.objects.count(), 2)
|
|
|
+
|
|
|
+ def test_306_retracing_one_joint_origin_keeps_its_sibling_pathed(self):
|
|
|
+ """
|
|
|
+ [IF1] --C1-- [IF2]
|
|
|
+ [IF3]
|
|
|
+
|
|
|
+ trace_paths retraces one endpoint at a time, so retiring a shared origin hop must restore the
|
|
|
+ co-origins it leaves behind.
|
|
|
+ """
|
|
|
+ interface1 = Interface.objects.create(device=self.device, name='Interface 1')
|
|
|
+ interface2 = Interface.objects.create(device=self.device, name='Interface 2')
|
|
|
+ interface3 = Interface.objects.create(device=self.device, name='Interface 3')
|
|
|
+
|
|
|
+ cable1 = Cable(a_terminations=[interface1], b_terminations=[interface2, interface3])
|
|
|
+ cable1.save()
|
|
|
+ self.assertEqual(CablePath.objects.count(), 2)
|
|
|
+
|
|
|
+ # trace_paths selects on a null _path
|
|
|
+ Interface.objects.filter(pk=interface2.pk).update(_path=None)
|
|
|
+
|
|
|
+ call_command('trace_paths', no_input=True, stdout=StringIO())
|
|
|
+
|
|
|
+ for interface in (interface1, interface2, interface3):
|
|
|
+ interface.refresh_from_db()
|
|
|
+ self.assertIsNotNone(interface._path_id, msg=f'{interface} left without a path')
|
|
|
+ self.assertEqual(
|
|
|
+ CablePath.objects.count(), 3, msg='the joint hop should split into one path per co-origin'
|
|
|
+ )
|
|
|
|
|
|
def test_401_exclude_midspan_devices(self):
|
|
|
"""
|