mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-20 10:16:42 -06:00
Update cable tracing logic
This commit is contained in:
parent
82706eb3a6
commit
f0b722b0a5
@ -16,7 +16,7 @@ class Migration(migrations.Migration):
|
|||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||||
('cable_end', models.CharField(max_length=1)),
|
('cable_end', models.CharField(max_length=1)),
|
||||||
('termination_id', models.PositiveBigIntegerField()),
|
('termination_id', models.PositiveBigIntegerField()),
|
||||||
('cable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='dcim.cable')),
|
('cable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dcim.cable')),
|
||||||
('termination_type', models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
|
('termination_type', models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
@ -92,10 +92,12 @@ class Cable(NetBoxModel):
|
|||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
terminations = []
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('pk',)
|
ordering = ('pk',)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, terminations=None, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
# A copy of the PK to be used by __str__ in case the object is deleted
|
# A copy of the PK to be used by __str__ in case the object is deleted
|
||||||
@ -104,19 +106,29 @@ class Cable(NetBoxModel):
|
|||||||
# Cache the original status so we can check later if it's been changed
|
# Cache the original status so we can check later if it's been changed
|
||||||
self._orig_status = self.status
|
self._orig_status = self.status
|
||||||
|
|
||||||
# @classmethod
|
# Assign associated CableTerminations (if any)
|
||||||
# def from_db(cls, db, field_names, values):
|
if terminations:
|
||||||
# """
|
assert type(terminations) is list
|
||||||
# Cache the original A and B terminations of existing Cable instances for later reference inside clean().
|
assert self.pk is None
|
||||||
# """
|
for t in terminations:
|
||||||
# instance = super().from_db(db, field_names, values)
|
t.cable = self
|
||||||
#
|
self.terminations.append(t)
|
||||||
# instance._orig_termination_a_type_id = instance.termination_a_type_id
|
|
||||||
# instance._orig_termination_a_ids = instance.termination_a_ids
|
@classmethod
|
||||||
# instance._orig_termination_b_type_id = instance.termination_b_type_id
|
def from_db(cls, db, field_names, values):
|
||||||
# instance._orig_termination_b_ids = instance.termination_b_ids
|
"""
|
||||||
#
|
Cache the original A and B terminations of existing Cable instances for later reference inside clean().
|
||||||
# return instance
|
"""
|
||||||
|
instance = super().from_db(db, field_names, values)
|
||||||
|
|
||||||
|
instance.terminations = CableTermination.objects.filter(cable=instance)
|
||||||
|
|
||||||
|
# instance._orig_termination_a_type_id = instance.termination_a_type_id
|
||||||
|
# instance._orig_termination_a_ids = instance.termination_a_ids
|
||||||
|
# instance._orig_termination_b_type_id = instance.termination_b_type_id
|
||||||
|
# instance._orig_termination_b_ids = instance.termination_b_ids
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
pk = self.pk or self._pk
|
pk = self.pk or self._pk
|
||||||
@ -186,7 +198,7 @@ class CableTermination(models.Model):
|
|||||||
cable = models.ForeignKey(
|
cable = models.ForeignKey(
|
||||||
to='dcim.Cable',
|
to='dcim.Cable',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='terminations'
|
related_name='+'
|
||||||
)
|
)
|
||||||
cable_end = models.CharField(
|
cable_end = models.CharField(
|
||||||
max_length=1,
|
max_length=1,
|
||||||
@ -296,59 +308,80 @@ class CablePath(models.Model):
|
|||||||
return f"Path #{self.pk}: {len(self.path)} nodes{status}"
|
return f"Path #{self.pk}: {len(self.path)} nodes{status}"
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
# Save the flattened nodes list
|
# Save the flattened nodes list
|
||||||
self._nodes = flatten_path(self.path)
|
self._nodes = flatten_path(self.path)
|
||||||
|
|
||||||
# TODO
|
super().save(*args, **kwargs)
|
||||||
# Record a direct reference to this CablePath on its originating object
|
|
||||||
# model = self.origin._meta.model
|
# Record a direct reference to this CablePath on its originating object(s)
|
||||||
# model.objects.filter(pk=self.origin.pk).update(_path=self.pk)
|
origins = [path_node_to_object(n) for n in self.path[0]]
|
||||||
|
origin_model = origins[0]._meta.model
|
||||||
|
origin_ids = [o.id for o in origins]
|
||||||
|
origin_model.objects.filter(pk__in=origin_ids).update(_path=self.pk)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def segment_count(self):
|
def segment_count(self):
|
||||||
return int(len(self.path) / 3)
|
return int(len(self.path) / 3)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_origin(cls, origin):
|
def from_origin(cls, terminations):
|
||||||
"""
|
"""
|
||||||
Create a new CablePath instance as traced from the given path origin.
|
Create a new CablePath instance as traced from the given path origin.
|
||||||
"""
|
"""
|
||||||
from circuits.models import CircuitTermination
|
from circuits.models import CircuitTermination
|
||||||
|
|
||||||
if origin is None or origin.link is None:
|
if not terminations or terminations[0].termination.link is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
destination = None
|
|
||||||
path = []
|
path = []
|
||||||
position_stack = []
|
position_stack = []
|
||||||
is_active = True
|
is_active = True
|
||||||
is_split = False
|
is_split = False
|
||||||
|
|
||||||
node = origin
|
# Start building the path from its originating CableTerminations
|
||||||
|
path.append([
|
||||||
|
object_to_path_node(t.termination) for t in terminations
|
||||||
|
])
|
||||||
|
|
||||||
|
node = terminations[0].termination
|
||||||
while node.link is not None:
|
while node.link is not None:
|
||||||
if hasattr(node.link, 'status') and node.link.status != LinkStatusChoices.STATUS_CONNECTED:
|
if hasattr(node.link, 'status') and node.link.status != LinkStatusChoices.STATUS_CONNECTED:
|
||||||
is_active = False
|
is_active = False
|
||||||
|
|
||||||
|
# Append the cable
|
||||||
|
path.append([object_to_path_node(node.link)])
|
||||||
|
|
||||||
# Follow the link to its far-end termination
|
# Follow the link to its far-end termination
|
||||||
path.append(object_to_path_node(node.link))
|
if terminations[0].cable_end == 'A':
|
||||||
peer_termination = node.get_link_peer()
|
peer_terminations = CableTermination.objects.filter(cable=terminations[0].cable, cable_end='B')
|
||||||
|
else:
|
||||||
|
peer_terminations = CableTermination.objects.filter(cable=terminations[0].cable, cable_end='A')
|
||||||
|
|
||||||
# Follow a FrontPort to its corresponding RearPort
|
# Follow FrontPorts to their corresponding RearPorts
|
||||||
if isinstance(peer_termination, FrontPort):
|
if isinstance(peer_terminations[0].termination, FrontPort):
|
||||||
path.append(object_to_path_node(peer_termination))
|
path.append([
|
||||||
node = peer_termination.rear_port
|
object_to_path_node(t.termination) for t in peer_terminations
|
||||||
|
])
|
||||||
|
terminations = CableTermination.objects.filter(
|
||||||
|
termination_type=ContentType.objects.get_for_model(RearPort),
|
||||||
|
termination_id__in=[t.termination_id for t in peer_terminations]
|
||||||
|
)
|
||||||
|
node = terminations[0].termination
|
||||||
if node.positions > 1:
|
if node.positions > 1:
|
||||||
position_stack.append(peer_termination.rear_port_position)
|
position_stack.append(node.rear_port_position)
|
||||||
path.append(object_to_path_node(node))
|
path.append([
|
||||||
|
object_to_path_node(t.termination) for t in terminations
|
||||||
|
])
|
||||||
|
|
||||||
# Follow a RearPort to its corresponding FrontPort (if any)
|
# Follow RearPorts to their corresponding FrontPorts (if any)
|
||||||
elif isinstance(peer_termination, RearPort):
|
elif isinstance(peer_terminations[0], RearPort):
|
||||||
path.append(object_to_path_node(peer_termination))
|
path.append([
|
||||||
|
object_to_path_node(t.termination) for t in peer_terminations
|
||||||
|
])
|
||||||
|
|
||||||
# Determine the peer FrontPort's position
|
# Determine the peer FrontPort's position
|
||||||
if peer_termination.positions == 1:
|
if peer_terminations[0].termination.positions == 1:
|
||||||
position = 1
|
position = 1
|
||||||
elif position_stack:
|
elif position_stack:
|
||||||
position = position_stack.pop()
|
position = position_stack.pop()
|
||||||
@ -357,41 +390,55 @@ class CablePath(models.Model):
|
|||||||
is_split = True
|
is_split = True
|
||||||
break
|
break
|
||||||
|
|
||||||
try:
|
# Map FrontPorts to their corresponding RearPorts
|
||||||
node = FrontPort.objects.get(rear_port=peer_termination, rear_port_position=position)
|
terminations = FrontPort.objects.filter(
|
||||||
path.append(object_to_path_node(node))
|
rear_port_id__in=[t.rear_port_id for t in peer_terminations],
|
||||||
except ObjectDoesNotExist:
|
rear_port_position=position
|
||||||
# No corresponding FrontPort found for the RearPort
|
)
|
||||||
break
|
if terminations:
|
||||||
|
path.append([
|
||||||
|
object_to_path_node(t.termination) for t in terminations
|
||||||
|
])
|
||||||
|
|
||||||
# Follow a CircuitTermination to its corresponding CircuitTermination (A to Z or vice versa)
|
# Follow a CircuitTermination to its corresponding CircuitTermination (A to Z or vice versa)
|
||||||
elif isinstance(peer_termination, CircuitTermination):
|
elif isinstance(peer_terminations[0], CircuitTermination):
|
||||||
path.append(object_to_path_node(peer_termination))
|
path.append([
|
||||||
# Get peer CircuitTermination
|
object_to_path_node(t.termination) for t in peer_terminations
|
||||||
node = peer_termination.get_peer_termination()
|
])
|
||||||
if node:
|
|
||||||
path.append(object_to_path_node(node))
|
# Get peer CircuitTerminations
|
||||||
if node.provider_network:
|
term_side = 'Z' if peer_terminations[0].termination == 'A' else 'Z'
|
||||||
destination = node.provider_network
|
terminations = CircuitTermination.objects.filter(
|
||||||
break
|
circuit=peer_terminations[0].circuit,
|
||||||
elif node.site and not node.cable:
|
term_side=term_side
|
||||||
destination = node.site
|
)
|
||||||
break
|
# Tracing across multiple circuits not currently supported
|
||||||
|
if len(terminations) > 1:
|
||||||
|
is_split = True
|
||||||
|
break
|
||||||
|
elif terminations:
|
||||||
|
path.append([
|
||||||
|
object_to_path_node(t.termination) for t in terminations
|
||||||
|
])
|
||||||
|
# TODO
|
||||||
|
# if node.provider_network:
|
||||||
|
# destination = node.provider_network
|
||||||
|
# break
|
||||||
|
# elif node.site and not node.cable:
|
||||||
|
# destination = node.site
|
||||||
|
# break
|
||||||
else:
|
else:
|
||||||
# No peer CircuitTermination exists; halt the trace
|
# No peer CircuitTermination exists; halt the trace
|
||||||
break
|
break
|
||||||
|
|
||||||
# Anything else marks the end of the path
|
# Anything else marks the end of the path
|
||||||
else:
|
else:
|
||||||
destination = peer_termination
|
path.append([
|
||||||
|
object_to_path_node(t.termination) for t in peer_terminations
|
||||||
|
])
|
||||||
break
|
break
|
||||||
|
|
||||||
if destination is None:
|
|
||||||
is_active = False
|
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
origin=origin,
|
|
||||||
destination=destination,
|
|
||||||
path=path,
|
path=path,
|
||||||
is_active=is_active,
|
is_active=is_active,
|
||||||
is_split=is_split
|
is_split=is_split
|
||||||
|
@ -79,15 +79,30 @@ def update_connected_endpoints(instance, created, raw=False, **kwargs):
|
|||||||
logger.debug(f"Skipping endpoint updates for imported cable {instance}")
|
logger.debug(f"Skipping endpoint updates for imported cable {instance}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO: Update link peer fields
|
# Save any new CableTerminations
|
||||||
|
CableTermination.objects.bulk_create([
|
||||||
|
term for term in instance.terminations if not term.pk
|
||||||
|
])
|
||||||
|
|
||||||
# # Create/update cable paths
|
# TODO: Update link peers
|
||||||
# if created:
|
# Set cable on terminating endpoints
|
||||||
# for term in instance.terminations.all():
|
_terms = {
|
||||||
# if isinstance(term.termination, PathEndpoint):
|
'A': [],
|
||||||
# create_cablepath(term.termination)
|
'B': [],
|
||||||
# else:
|
}
|
||||||
# rebuild_paths(term.termination)
|
for term in instance.terminations:
|
||||||
|
if term.termination.cable != instance:
|
||||||
|
term.termination.cable = instance
|
||||||
|
term.termination.save()
|
||||||
|
_terms[term.cable_end].append(term)
|
||||||
|
|
||||||
|
# Create/update cable paths
|
||||||
|
if created:
|
||||||
|
for terms in _terms.values():
|
||||||
|
if isinstance(terms[0].termination, PathEndpoint):
|
||||||
|
create_cablepath(terms)
|
||||||
|
else:
|
||||||
|
rebuild_paths(terms)
|
||||||
# elif instance.status != instance._orig_status:
|
# elif instance.status != instance._orig_status:
|
||||||
# # We currently don't support modifying either termination of an existing Cable. (This
|
# # We currently don't support modifying either termination of an existing Cable. (This
|
||||||
# # may change in the future.) However, we do need to capture status changes and update
|
# # may change in the future.) However, we do need to capture status changes and update
|
||||||
@ -98,17 +113,17 @@ def update_connected_endpoints(instance, created, raw=False, **kwargs):
|
|||||||
# rebuild_paths(instance)
|
# rebuild_paths(instance)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=CableTermination)
|
# @receiver(post_save, sender=CableTermination)
|
||||||
def cache_cable_on_endpoints(instance, created, raw=False, **kwargs):
|
# def cache_cable_on_endpoints(instance, created, raw=False, **kwargs):
|
||||||
if not raw:
|
# if not raw:
|
||||||
model = instance.termination_type.model_class()
|
# model = instance.termination_type.model_class()
|
||||||
model.objects.filter(pk=instance.termination_id).update(cable=instance.cable)
|
# model.objects.filter(pk=instance.termination_id).update(cable=instance.cable)
|
||||||
|
#
|
||||||
|
#
|
||||||
@receiver(post_delete, sender=CableTermination)
|
# @receiver(post_delete, sender=CableTermination)
|
||||||
def clear_cable_on_endpoints(instance, **kwargs):
|
# def clear_cable_on_endpoints(instance, **kwargs):
|
||||||
model = instance.termination_type.model_class()
|
# model = instance.termination_type.model_class()
|
||||||
model.objects.filter(pk=instance.termination_id).update(cable=None)
|
# model.objects.filter(pk=instance.termination_id).update(cable=None)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Cable)
|
@receiver(post_delete, sender=Cable)
|
||||||
@ -129,15 +144,17 @@ def nullify_connected_endpoints(instance, **kwargs):
|
|||||||
# model.objects.filter(pk__in=instance.termination_b_ids).update(_link_peer_type=None, _link_peer_id=None)
|
# model.objects.filter(pk__in=instance.termination_b_ids).update(_link_peer_type=None, _link_peer_id=None)
|
||||||
|
|
||||||
# Delete and retrace any dependent cable paths
|
# Delete and retrace any dependent cable paths
|
||||||
for cablepath in CablePath.objects.filter(path__contains=instance):
|
for cablepath in CablePath.objects.filter(_nodes__contains=instance):
|
||||||
cp = CablePath.from_origin(cablepath.origin)
|
cablepath.delete()
|
||||||
if cp:
|
# TODO: Create new traces
|
||||||
CablePath.objects.filter(pk=cablepath.pk).update(
|
# cp = CablePath.from_origin(cablepath.origin)
|
||||||
_nodes=cp._nodes,
|
# if cp:
|
||||||
destination_type=ContentType.objects.get_for_model(cp.destination) if cp.destination else None,
|
# CablePath.objects.filter(pk=cablepath.pk).update(
|
||||||
destination_id=cp.destination.pk if cp.destination else None,
|
# _nodes=cp._nodes,
|
||||||
is_active=cp.is_active,
|
# destination_type=ContentType.objects.get_for_model(cp.destination) if cp.destination else None,
|
||||||
is_split=cp.is_split
|
# destination_id=cp.destination.pk if cp.destination else None,
|
||||||
)
|
# is_active=cp.is_active,
|
||||||
else:
|
# is_split=cp.is_split
|
||||||
cablepath.delete()
|
# )
|
||||||
|
# else:
|
||||||
|
# cablepath.delete()
|
||||||
|
Loading…
Reference in New Issue
Block a user