From 6a42ca3ff20ab2e0160085c06d6b1e3fae586277 Mon Sep 17 00:00:00 2001 From: Brian Tiemann Date: Tue, 19 Nov 2024 10:02:40 -0500 Subject: [PATCH] Add max_length param to from_origin to support testing --- netbox/circuits/signals.py | 4 ++-- netbox/dcim/models/cables.py | 21 ++++++++++----------- netbox/dcim/signals.py | 20 ++++++++++---------- netbox/dcim/utils.py | 9 +++++---- netbox/wireless/signals.py | 4 ++-- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/netbox/circuits/signals.py b/netbox/circuits/signals.py index 70f2abb41..8c8c0d2aa 100644 --- a/netbox/circuits/signals.py +++ b/netbox/circuits/signals.py @@ -17,11 +17,11 @@ def update_circuit(instance, **kwargs): @receiver((post_save, post_delete), sender=CircuitTermination) -def rebuild_cablepaths(instance, raw=False, **kwargs): +def rebuild_cablepaths(instance, raw=False, max_length=None, **kwargs): """ Rebuild any CablePaths which traverse the peer CircuitTermination. """ if not raw: peer_termination = instance.get_peer_termination() if peer_termination: - rebuild_paths([peer_termination]) + rebuild_paths([peer_termination], max_length=max_length) diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index e2be2944f..745a146f2 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -204,6 +204,7 @@ class Cable(PrimaryModel): def save(self, *args, **kwargs): _created = self.pk is None + max_length = kwargs.pop('max_length', None) # Store the given length (if any) in meters for use in database ordering if self.length is not None and self.length_unit: @@ -242,7 +243,7 @@ class Cable(PrimaryModel): if not termination.pk or termination not in b_terminations: CableTermination(cable=self, cable_end='B', termination=termination).save() - trace_paths.send(Cable, instance=self, created=_created) + trace_paths.send(Cable, instance=self, created=_created, max_length=max_length) def get_status_color(self): return LinkStatusChoices.colors.get(self.status) @@ -524,7 +525,7 @@ class CablePath(models.Model): return int(len(self.path) / 3) @classmethod - def from_origin(cls, terminations): + def from_origin(cls, terminations, max_length=None): """ Create a new CablePath instance as traced from the given termination objects. These can be any object to which a Cable or WirelessLink connects (interfaces, console ports, circuit termination, etc.). All terminations must be @@ -532,6 +533,8 @@ class CablePath(models.Model): """ from circuits.models import CircuitTermination + max_length = max_length or 99999 + if not terminations: return None @@ -587,20 +590,16 @@ class CablePath(models.Model): # Step 4: Record the links, keeping cables in order to allow for SVG rendering cables = [] - loop_detected = False for link in links: cable = object_to_path_node(link) if cable not in cables: - # Detect infinite loop in cabling topology - for node in path: - if cable in node: - loop_detected = True cables.append(cable) - if loop_detected: + path.append(cables) + print(len(path), max_length) + if len(path) > max_length: logger.warning('Infinite loop detected while updating cable path trace') break - path.append(cables) # Step 5: Update the path status if a link is not connected links_status = [link.status for link in links if link.status != LinkStatusChoices.STATUS_CONNECTED] @@ -742,11 +741,11 @@ class CablePath(models.Model): is_split=is_split ) - def retrace(self): + def retrace(self, max_length=None): """ Retrace the path from the currently-defined originating termination(s) """ - _new = self.from_origin(self.origins) + _new = self.from_origin(self.origins, max_length=max_length) if _new: self.path = _new.path self.is_complete = _new.is_complete diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py index a51872719..4f3cb1d9d 100644 --- a/netbox/dcim/signals.py +++ b/netbox/dcim/signals.py @@ -72,7 +72,7 @@ def clear_virtualchassis_members(instance, **kwargs): # @receiver(trace_paths, sender=Cable) -def update_connected_endpoints(instance, created, raw=False, **kwargs): +def update_connected_endpoints(instance, created, raw=False, max_length=None, **kwargs): """ When a Cable is saved with new terminations, retrace any affected cable paths. """ @@ -95,29 +95,29 @@ def update_connected_endpoints(instance, created, raw=False, **kwargs): if not nodes: continue if isinstance(nodes[0], PathEndpoint): - create_cablepath(nodes) + create_cablepath(nodes, max_length=max_length) else: - rebuild_paths(nodes) + rebuild_paths(nodes, max_length=max_length) # Update status of CablePaths if Cable status has been changed elif instance.status != instance._orig_status: if instance.status != LinkStatusChoices.STATUS_CONNECTED: CablePath.objects.filter(_nodes__contains=instance).update(is_active=False) else: - rebuild_paths([instance]) + rebuild_paths([instance], max_length=max_length) @receiver(post_delete, sender=Cable) -def retrace_cable_paths(instance, **kwargs): +def retrace_cable_paths(instance, max_length=None, **kwargs): """ When a Cable is deleted, check for and update its connected endpoints """ for cablepath in CablePath.objects.filter(_nodes__contains=instance): - cablepath.retrace() + cablepath.retrace(max_length=max_length) @receiver(post_delete, sender=CableTermination) -def nullify_connected_endpoints(instance, **kwargs): +def nullify_connected_endpoints(instance, max_length=None, **kwargs): """ Disassociate the Cable from the termination object, and retrace any affected CablePaths. """ @@ -128,15 +128,15 @@ def nullify_connected_endpoints(instance, **kwargs): # Remove the deleted CableTermination if it's one of the path's originating nodes if instance.termination in cablepath.origins: cablepath.origins.remove(instance.termination) - cablepath.retrace() + cablepath.retrace(max_length=max_length) @receiver(post_save, sender=FrontPort) -def extend_rearport_cable_paths(instance, created, raw, **kwargs): +def extend_rearport_cable_paths(instance, created, raw, max_length=None, **kwargs): """ When a new FrontPort is created, add it to any CablePaths which end at its corresponding RearPort. """ if created and not raw: rearport = instance.rear_port for cablepath in CablePath.objects.filter(_nodes__contains=rearport): - cablepath.retrace() + cablepath.retrace(max_length=max_length) diff --git a/netbox/dcim/utils.py b/netbox/dcim/utils.py index 4d4228490..71064bed9 100644 --- a/netbox/dcim/utils.py +++ b/netbox/dcim/utils.py @@ -30,20 +30,21 @@ def path_node_to_object(repr): return ct.model_class().objects.filter(pk=object_id).first() -def create_cablepath(terminations): +def create_cablepath(terminations, max_length=None): """ Create CablePaths for all paths originating from the specified set of nodes. :param terminations: Iterable of CableTermination objects + :max_length: Optional limit of cable path to trace """ from dcim.models import CablePath - cp = CablePath.from_origin(terminations) + cp = CablePath.from_origin(terminations, max_length=max_length) if cp: cp.save() -def rebuild_paths(terminations): +def rebuild_paths(terminations, max_length=None): """ Rebuild all CablePaths which traverse the specified nodes. """ @@ -55,4 +56,4 @@ def rebuild_paths(terminations): with transaction.atomic(): for cp in cable_paths: cp.delete() - create_cablepath(cp.origins) + create_cablepath(cp.origins, max_length=max_length) diff --git a/netbox/wireless/signals.py b/netbox/wireless/signals.py index ff7b1229c..71cd64180 100644 --- a/netbox/wireless/signals.py +++ b/netbox/wireless/signals.py @@ -13,7 +13,7 @@ from .models import WirelessLink # @receiver(post_save, sender=WirelessLink) -def update_connected_interfaces(instance, created, raw=False, **kwargs): +def update_connected_interfaces(instance, created, raw=False, max_length=None, **kwargs): """ When a WirelessLink is saved, save a reference to it on each connected interface. """ @@ -34,7 +34,7 @@ def update_connected_interfaces(instance, created, raw=False, **kwargs): # Create/update cable paths if created: for interface in (instance.interface_a, instance.interface_b): - create_cablepath([interface]) + create_cablepath([interface], max_length=max_length) @receiver(post_delete, sender=WirelessLink)