Simplify profile mappings; misc cleanup

This commit is contained in:
Jeremy Stretch
2025-12-14 14:04:00 -05:00
parent fe52e4cd74
commit 97b9805b94
3 changed files with 119 additions and 83 deletions

View File

@@ -1,17 +1,24 @@
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from dcim.choices import CableEndChoices
from dcim.models import CableTermination from dcim.models import CableTermination
class BaseCableProfile: class BaseCableProfile:
"""Base class for representing a cable profile.""" """Base class for representing a cable profile."""
# Mappings of connectors to their available positions at either end of the cable. For example, a 12-strand MPO # Mappings of connectors to the number of positions presented by each, at either end of the cable. For example, a
# fiber cable would have one connector at either end with six positions (six bidirectional fiber pairs). # 12-strand MPO fiber cable would have one connector at either end with six positions (six bidirectional fiber
# pairs).
a_connectors = {} a_connectors = {}
b_connectors = {} b_connectors = {}
# Defined a mapping of A/B connector & position pairings. If not defined, all positions are presumed to be
# symmetrical (i.e. 1:1 on side A maps to 1:1 on side B). If defined, it must be constructed as a dictionary of
# two-item tuples, e.g. {(1, 1): (1, 1)}.
_mapping = None
def clean(self, cable): def clean(self, cable):
# Enforce maximum terminations limits # Enforce maximum terminations limits
a_terminations_count = len(cable.a_terminations) a_terminations_count = len(cable.a_terminations)
@@ -44,6 +51,8 @@ class BaseCableProfile:
Return the mapped far-end connector & position for a given cable end the local connector & position. Return the mapped far-end connector & position for a given cable end the local connector & position.
""" """
# By default, assume all positions are symmetrical. # By default, assume all positions are symmetrical.
if self._mapping:
return self._mapping.get((connector, position))
return connector, position return connector, position
def get_peer_termination(self, termination, position): def get_peer_termination(self, termination, position):
@@ -67,176 +76,182 @@ class BaseCableProfile:
return None, None return None, None
# Profile naming:
# - Single: One connector per side, with one or more positions
# - Trunk: Two or more connectors per side, with one or more positions per connector
# - Breakout: One or more connectors on the A side which map to a greater number of B side connectors
# - Shuffle: A cable with nonlinear position mappings between sides
class Single1C1PCableProfile(BaseCableProfile): class Single1C1PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1], 1: 1,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Single1C2PCableProfile(BaseCableProfile): class Single1C2PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2], 1: 2,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Single1C4PCableProfile(BaseCableProfile): class Single1C4PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4], 1: 4,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Single1C6PCableProfile(BaseCableProfile): class Single1C6PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4, 5, 6], 1: 6,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Single1C8PCableProfile(BaseCableProfile): class Single1C8PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4, 5, 6, 7, 8], 1: 8,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Single1C12PCableProfile(BaseCableProfile): class Single1C12PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 1: 12,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Single1C16PCableProfile(BaseCableProfile): class Single1C16PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 16], 1: 16,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Trunk2C1PCableProfile(BaseCableProfile): class Trunk2C1PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1], 1: 1,
2: [1], 2: 1,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Trunk2C2PCableProfile(BaseCableProfile): class Trunk2C2PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2], 1: 2,
2: [1, 2], 2: 2,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Trunk2C4PCableProfile(BaseCableProfile): class Trunk2C4PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4], 1: 4,
2: [1, 2, 3, 4], 2: 4,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Trunk2C6PCableProfile(BaseCableProfile): class Trunk2C6PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4, 5, 6], 1: 6,
2: [1, 2, 3, 4, 5, 6], 2: 6,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Trunk2C8PCableProfile(BaseCableProfile): class Trunk2C8PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4, 5, 6, 7, 8], 1: 8,
2: [1, 2, 3, 4, 5, 6, 7, 8], 2: 8,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Trunk2C12PCableProfile(BaseCableProfile): class Trunk2C12PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 1: 12,
2: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 2: 12,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Trunk4C1PCableProfile(BaseCableProfile): class Trunk4C1PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1], 1: 1,
2: [1], 2: 1,
3: [1], 3: 1,
4: [1], 4: 1,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Trunk4C2PCableProfile(BaseCableProfile): class Trunk4C2PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2], 1: 2,
2: [1, 2], 2: 2,
3: [1, 2], 3: 2,
4: [1, 2], 4: 2,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Trunk4C4PCableProfile(BaseCableProfile): class Trunk4C4PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4], 1: 4,
2: [1, 2, 3, 4], 2: 4,
3: [1, 2, 3, 4], 3: 4,
4: [1, 2, 3, 4], 4: 4,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Trunk4C6PCableProfile(BaseCableProfile): class Trunk4C6PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4, 5, 6], 1: 6,
2: [1, 2, 3, 4, 5, 6], 2: 6,
3: [1, 2, 3, 4, 5, 6], 3: 6,
4: [1, 2, 3, 4, 5, 6], 4: 6,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Trunk4C8PCableProfile(BaseCableProfile): class Trunk4C8PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4, 5, 6, 7, 8], 1: 8,
2: [1, 2, 3, 4, 5, 6, 7, 8], 2: 8,
3: [1, 2, 3, 4, 5, 6, 7, 8], 3: 8,
4: [1, 2, 3, 4, 5, 6, 7, 8], 4: 8,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Trunk8C4PCableProfile(BaseCableProfile): class Trunk8C4PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4], 1: 4,
2: [1, 2, 3, 4], 2: 4,
3: [1, 2, 3, 4], 3: 4,
4: [1, 2, 3, 4], 4: 4,
5: [1, 2, 3, 4], 5: 4,
6: [1, 2, 3, 4], 6: 4,
7: [1, 2, 3, 4], 7: 4,
8: [1, 2, 3, 4], 8: 4,
} }
b_connectors = a_connectors b_connectors = a_connectors
class Breakout1C4Px4C1PCableProfile(BaseCableProfile): class Breakout1C4Px4C1PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4], 1: 4,
} }
b_connectors = { b_connectors = {
1: [1], 1: 1,
2: [1], 2: 1,
3: [1], 3: 1,
4: [1], 4: 1,
} }
_mapping = { _mapping = {
(1, 1): (1, 1), (1, 1): (1, 1),
@@ -248,14 +263,38 @@ class Breakout1C4Px4C1PCableProfile(BaseCableProfile):
(4, 1): (1, 4), (4, 1): (1, 4),
} }
def get_mapped_position(self, side, connector, position):
return self._mapping.get((connector, position)) class Breakout1C6Px6C1PCableProfile(BaseCableProfile):
a_connectors = {
1: 6,
}
b_connectors = {
1: 1,
2: 1,
3: 1,
4: 1,
5: 1,
6: 1,
}
_mapping = {
(1, 1): (1, 1),
(1, 2): (2, 1),
(1, 3): (3, 1),
(1, 4): (4, 1),
(1, 5): (5, 1),
(1, 6): (6, 1),
(2, 1): (1, 2),
(3, 1): (1, 3),
(4, 1): (1, 4),
(5, 1): (1, 5),
(6, 1): (1, 6),
}
class Shuffle2C4PCableProfile(BaseCableProfile): class Shuffle2C4PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4], 1: 4,
2: [1, 2, 3, 4], 2: 4,
} }
b_connectors = a_connectors b_connectors = a_connectors
_mapping = { _mapping = {
@@ -269,16 +308,13 @@ class Shuffle2C4PCableProfile(BaseCableProfile):
(2, 4): (2, 4), (2, 4): (2, 4),
} }
def get_mapped_position(self, side, connector, position):
return self._mapping.get((connector, position))
class Shuffle4C4PCableProfile(BaseCableProfile): class Shuffle4C4PCableProfile(BaseCableProfile):
a_connectors = { a_connectors = {
1: [1, 2, 3, 4], 1: 4,
2: [1, 2, 3, 4], 2: 4,
3: [1, 2, 3, 4], 3: 4,
4: [1, 2, 3, 4], 4: 4,
} }
b_connectors = a_connectors b_connectors = a_connectors
_mapping = { _mapping = {
@@ -300,27 +336,24 @@ class Shuffle4C4PCableProfile(BaseCableProfile):
(4, 4): (4, 4), (4, 4): (4, 4),
} }
def get_mapped_position(self, side, connector, position):
return self._mapping.get((connector, position))
class ShuffleBreakout2x8CableProfile(BaseCableProfile): class ShuffleBreakout2x8CableProfile(BaseCableProfile):
""" """
Temporary solution for mapping 2 front/rear ports to 8 discrete interfaces Temporary solution for mapping 2 front/rear ports to 8 discrete interfaces
""" """
a_connectors = { a_connectors = {
1: [1, 2, 3, 4], 1: 4,
2: [1, 2, 3, 4], 2: 4,
} }
b_connectors = { b_connectors = {
1: [1], 1: 1,
2: [1], 2: 1,
3: [1], 3: 1,
4: [1], 4: 1,
5: [1], 5: 1,
6: [1], 6: 1,
7: [1], 7: 1,
8: [1], 8: 1,
} }
_a_mapping = { _a_mapping = {
(1, 1): (1, 1), (1, 1): (1, 1),
@@ -344,6 +377,6 @@ class ShuffleBreakout2x8CableProfile(BaseCableProfile):
} }
def get_mapped_position(self, side, connector, position): def get_mapped_position(self, side, connector, position):
if side.lower() == 'a': if side.lower() == CableEndChoices.SIDE_A:
return self._a_mapping.get((connector, position)) return self._a_mapping.get((connector, position))
return self._b_mapping.get((connector, position)) return self._b_mapping.get((connector, position))

View File

@@ -1745,6 +1745,7 @@ class CableProfileChoices(ChoiceSet):
TRUNK_8C4P = 'trunk-8c4p' TRUNK_8C4P = 'trunk-8c4p'
# Breakouts # Breakouts
BREAKOUT_1C4P_4C1P = 'breakout-1c4p-4c1p' BREAKOUT_1C4P_4C1P = 'breakout-1c4p-4c1p'
BREAKOUT_1C6P_6C1P = 'breakout-1c6p-6c1p'
SHUFFLE_BREAKOUT_2X8 = 'shuffle-breakout-2x8' SHUFFLE_BREAKOUT_2X8 = 'shuffle-breakout-2x8'
# Shuffles # Shuffles
SHUFFLE_2C4P = 'shuffle-2c4p' SHUFFLE_2C4P = 'shuffle-2c4p'
@@ -1781,13 +1782,14 @@ class CableProfileChoices(ChoiceSet):
), ),
), ),
( (
_('Breakouts'), _('Breakout'),
( (
(BREAKOUT_1C4P_4C1P, _('Breakout (1C4P/4C1P)')), (BREAKOUT_1C4P_4C1P, _('Breakout (1C4P/4C1P)')),
(BREAKOUT_1C6P_6C1P, _('Breakout (1C6P/6C1P)')),
), ),
), ),
( (
_('Shuffles'), _('Shuffle'),
( (
(SHUFFLE_2C4P, _('Shuffle (2C4P)')), (SHUFFLE_2C4P, _('Shuffle (2C4P)')),
(SHUFFLE_4C4P, _('Shuffle (4C4P)')), (SHUFFLE_4C4P, _('Shuffle (4C4P)')),

View File

@@ -157,6 +157,7 @@ class Cable(PrimaryModel):
CableProfileChoices.TRUNK_4C8P: cable_profiles.Trunk4C8PCableProfile, CableProfileChoices.TRUNK_4C8P: cable_profiles.Trunk4C8PCableProfile,
CableProfileChoices.TRUNK_8C4P: cable_profiles.Trunk8C4PCableProfile, CableProfileChoices.TRUNK_8C4P: cable_profiles.Trunk8C4PCableProfile,
CableProfileChoices.BREAKOUT_1C4P_4C1P: cable_profiles.Breakout1C4Px4C1PCableProfile, CableProfileChoices.BREAKOUT_1C4P_4C1P: cable_profiles.Breakout1C4Px4C1PCableProfile,
CableProfileChoices.BREAKOUT_1C6P_6C1P: cable_profiles.Breakout1C6Px6C1PCableProfile,
CableProfileChoices.SHUFFLE_2C4P: cable_profiles.Shuffle2C4PCableProfile, CableProfileChoices.SHUFFLE_2C4P: cable_profiles.Shuffle2C4PCableProfile,
CableProfileChoices.SHUFFLE_4C4P: cable_profiles.Shuffle4C4PCableProfile, CableProfileChoices.SHUFFLE_4C4P: cable_profiles.Shuffle4C4PCableProfile,
CableProfileChoices.SHUFFLE_BREAKOUT_2X8: cable_profiles.ShuffleBreakout2x8CableProfile, CableProfileChoices.SHUFFLE_BREAKOUT_2X8: cable_profiles.ShuffleBreakout2x8CableProfile,
@@ -363,7 +364,7 @@ class Cable(PrimaryModel):
connector = positions = None connector = positions = None
if self.profile: if self.profile:
connector = i connector = i
positions = self.profile_class().a_connectors[i] positions = list(range(1, self.profile_class().a_connectors[i] + 1))
CableTermination( CableTermination(
cable=self, cable=self,
cable_end='A', cable_end='A',
@@ -376,7 +377,7 @@ class Cable(PrimaryModel):
connector = positions = None connector = positions = None
if self.profile: if self.profile:
connector = i connector = i
positions = self.profile_class().b_connectors[i] positions = list(range(1, self.profile_class().b_connectors[i] + 1))
CableTermination( CableTermination(
cable=self, cable=self,
cable_end='B', cable_end='B',