Skip to content

Commit 24c6653

Browse files
committed
Add profile for 4x4 MPO8 shuffle cable
1 parent fb2ea37 commit 24c6653

File tree

4 files changed

+155
-10
lines changed

4 files changed

+155
-10
lines changed

netbox/dcim/cable_profiles.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ def clean(self, cable):
4444
)
4545
})
4646

47-
def get_mapped_position(self, position):
47+
def get_mapped_position(self, side, position):
48+
"""
49+
Return the mapped position for a given cable end and position.
50+
51+
By default, assume all positions are symmetrical.
52+
"""
4853
return position
4954

5055
def get_peer_terminations(self, terminations, position_stack):
@@ -65,7 +70,7 @@ def get_peer_terminations(self, terminations, position_stack):
6570
cable_end=terminations[0].opposite_cable_end
6671
)
6772
if position is not None:
68-
qs = qs.filter(position=self.get_mapped_position(position))
73+
qs = qs.filter(position=self.get_mapped_position(local_end, position))
6974
return qs
7075

7176

@@ -93,11 +98,11 @@ class BToManyCableProfile(BaseCableProfile):
9398
b_side_numbered = False
9499

95100

96-
class Shuffle2x2MPOCableProfile(BaseCableProfile):
101+
class Shuffle2x2MPO8CableProfile(BaseCableProfile):
97102
a_max_connections = 8
98103
b_max_connections = 8
99104

100-
def get_mapped_position(self, position):
105+
def get_mapped_position(self, side, position):
101106
return {
102107
1: 1,
103108
2: 2,
@@ -108,3 +113,26 @@ def get_mapped_position(self, position):
108113
7: 7,
109114
8: 8,
110115
}.get(position)
116+
117+
118+
class Shuffle4x4MPO8CableProfile(BaseCableProfile):
119+
a_max_connections = 8
120+
b_max_connections = 8
121+
# A side to B side position mapping
122+
_a_mapping = {
123+
1: 1,
124+
2: 3,
125+
3: 5,
126+
4: 7,
127+
5: 2,
128+
6: 4,
129+
7: 6,
130+
8: 8,
131+
}
132+
# B side to A side position mapping
133+
_b_mapping = {v: k for k, v in _a_mapping.items()}
134+
135+
def get_mapped_position(self, side, position):
136+
if side.lower() == 'b':
137+
return self._b_mapping.get(position)
138+
return self._a_mapping.get(position)

netbox/dcim/choices.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,15 +1722,17 @@ class CableProfileChoices(ChoiceSet):
17221722
STRAIGHT_MULTI = 'straight-multi'
17231723
A_TO_MANY = 'a-to-many'
17241724
B_TO_MANY = 'b-to-many'
1725-
SHUFFLE_2X2_MPO = 'shuffle-2x2-mpo'
1725+
SHUFFLE_2X2_MPO8 = 'shuffle-2x2-mpo8'
1726+
SHUFFLE_4X4_MPO8 = 'shuffle-4x4-mpo8'
17261727

17271728
CHOICES = (
17281729
(STRAIGHT_SINGLE, _('Straight (single position)')),
17291730
(STRAIGHT_MULTI, _('Straight (multi-position)')),
17301731
# TODO: Better names for many-to-one profiles?
17311732
(A_TO_MANY, _('A to many')),
17321733
(B_TO_MANY, _('B to many')),
1733-
(SHUFFLE_2X2_MPO, _('Shuffle (2x2 MPO)')),
1734+
(SHUFFLE_2X2_MPO8, _('Shuffle (2x2 MPO8)')),
1735+
(SHUFFLE_4X4_MPO8, _('Shuffle (4x4 MPO8)')),
17341736
)
17351737

17361738

netbox/dcim/models/cables.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ def profile_class(self):
138138
CableProfileChoices.STRAIGHT_MULTI: cable_profiles.StraightMultiCableProfile,
139139
CableProfileChoices.A_TO_MANY: cable_profiles.AToManyCableProfile,
140140
CableProfileChoices.B_TO_MANY: cable_profiles.BToManyCableProfile,
141-
CableProfileChoices.SHUFFLE_2X2_MPO: cable_profiles.Shuffle2x2MPOCableProfile,
141+
CableProfileChoices.SHUFFLE_2X2_MPO8: cable_profiles.Shuffle2x2MPO8CableProfile,
142+
CableProfileChoices.SHUFFLE_4X4_MPO8: cable_profiles.Shuffle4x4MPO8CableProfile,
142143
}.get(self.profile)
143144

144145
def _get_x_terminations(self, side):

netbox/dcim/tests/test_cablepaths2.py

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def test_104_cable_profile_b_to_many(self):
262262
# Check that all CablePaths have been deleted
263263
self.assertEqual(CablePath.objects.count(), 0)
264264

265-
def test_105_cable_profile_2x2_mpo(self):
265+
def test_105_cable_profile_2x2_mpo8(self):
266266
"""
267267
[IF1:1] --C1-- [IF3:1]
268268
[IF1:2] [IF3:2]
@@ -273,9 +273,10 @@ def test_105_cable_profile_2x2_mpo(self):
273273
[IF2:3] [IF4:3]
274274
[IF2:4] [IF4:4]
275275
276-
Cable profile: Shuffle (2x2 MPO)
276+
Cable profile: Shuffle (2x2 MPO8)
277277
"""
278278
interfaces = [
279+
# A side
279280
Interface.objects.create(device=self.device, name='Interface 1:1'),
280281
Interface.objects.create(device=self.device, name='Interface 1:2'),
281282
Interface.objects.create(device=self.device, name='Interface 1:3'),
@@ -284,6 +285,7 @@ def test_105_cable_profile_2x2_mpo(self):
284285
Interface.objects.create(device=self.device, name='Interface 2:2'),
285286
Interface.objects.create(device=self.device, name='Interface 2:3'),
286287
Interface.objects.create(device=self.device, name='Interface 2:4'),
288+
# B side
287289
Interface.objects.create(device=self.device, name='Interface 3:1'),
288290
Interface.objects.create(device=self.device, name='Interface 3:2'),
289291
Interface.objects.create(device=self.device, name='Interface 3:3'),
@@ -296,7 +298,7 @@ def test_105_cable_profile_2x2_mpo(self):
296298

297299
# Create cable 1
298300
cable1 = Cable(
299-
profile=CableProfileChoices.SHUFFLE_2X2_MPO,
301+
profile=CableProfileChoices.SHUFFLE_2X2_MPO8,
300302
a_terminations=interfaces[0:8],
301303
b_terminations=interfaces[8:16],
302304
)
@@ -372,6 +374,118 @@ def test_105_cable_profile_2x2_mpo(self):
372374
# Check that all CablePaths have been deleted
373375
self.assertEqual(CablePath.objects.count(), 0)
374376

377+
def test_106_cable_profile_4x4_mpo8(self):
378+
"""
379+
[IF1:1] --C1-- [IF3:1]
380+
[IF1:2] [IF3:2]
381+
[IF1:3] [IF3:3]
382+
[IF1:4] [IF3:4]
383+
[IF2:1] [IF4:1]
384+
[IF2:2] [IF4:2]
385+
[IF2:3] [IF4:3]
386+
[IF2:4] [IF4:4]
387+
388+
Cable profile: Shuffle (4x4 MPO8)
389+
"""
390+
interfaces = [
391+
# A side
392+
Interface.objects.create(device=self.device, name='Interface 1:1'),
393+
Interface.objects.create(device=self.device, name='Interface 1:2'),
394+
Interface.objects.create(device=self.device, name='Interface 2:1'),
395+
Interface.objects.create(device=self.device, name='Interface 2:2'),
396+
Interface.objects.create(device=self.device, name='Interface 3:1'),
397+
Interface.objects.create(device=self.device, name='Interface 3:2'),
398+
Interface.objects.create(device=self.device, name='Interface 4:1'),
399+
Interface.objects.create(device=self.device, name='Interface 4:2'),
400+
# B side
401+
Interface.objects.create(device=self.device, name='Interface 5:1'),
402+
Interface.objects.create(device=self.device, name='Interface 5:2'),
403+
Interface.objects.create(device=self.device, name='Interface 6:1'),
404+
Interface.objects.create(device=self.device, name='Interface 6:2'),
405+
Interface.objects.create(device=self.device, name='Interface 7:1'),
406+
Interface.objects.create(device=self.device, name='Interface 7:2'),
407+
Interface.objects.create(device=self.device, name='Interface 8:1'),
408+
Interface.objects.create(device=self.device, name='Interface 8:2'),
409+
]
410+
411+
# Create cable 1
412+
cable1 = Cable(
413+
profile=CableProfileChoices.SHUFFLE_4X4_MPO8,
414+
a_terminations=interfaces[0:8],
415+
b_terminations=interfaces[8:16],
416+
)
417+
cable1.clean()
418+
cable1.save()
419+
420+
paths = [
421+
# A-to-B paths
422+
self.assertPathExists(
423+
(interfaces[0], cable1, interfaces[8]), is_complete=True, is_active=True
424+
),
425+
self.assertPathExists(
426+
(interfaces[1], cable1, interfaces[10]), is_complete=True, is_active=True
427+
),
428+
self.assertPathExists(
429+
(interfaces[2], cable1, interfaces[12]), is_complete=True, is_active=True
430+
),
431+
self.assertPathExists(
432+
(interfaces[3], cable1, interfaces[14]), is_complete=True, is_active=True
433+
),
434+
self.assertPathExists(
435+
(interfaces[4], cable1, interfaces[9]), is_complete=True, is_active=True
436+
),
437+
self.assertPathExists(
438+
(interfaces[5], cable1, interfaces[11]), is_complete=True, is_active=True
439+
),
440+
self.assertPathExists(
441+
(interfaces[6], cable1, interfaces[13]), is_complete=True, is_active=True
442+
),
443+
self.assertPathExists(
444+
(interfaces[7], cable1, interfaces[15]), is_complete=True, is_active=True
445+
),
446+
# B-to-A paths
447+
self.assertPathExists(
448+
(interfaces[8], cable1, interfaces[0]), is_complete=True, is_active=True
449+
),
450+
self.assertPathExists(
451+
(interfaces[9], cable1, interfaces[4]), is_complete=True, is_active=True
452+
),
453+
self.assertPathExists(
454+
(interfaces[10], cable1, interfaces[1]), is_complete=True, is_active=True
455+
),
456+
self.assertPathExists(
457+
(interfaces[11], cable1, interfaces[5]), is_complete=True, is_active=True
458+
),
459+
self.assertPathExists(
460+
(interfaces[12], cable1, interfaces[2]), is_complete=True, is_active=True
461+
),
462+
self.assertPathExists(
463+
(interfaces[13], cable1, interfaces[6]), is_complete=True, is_active=True
464+
),
465+
self.assertPathExists(
466+
(interfaces[14], cable1, interfaces[3]), is_complete=True, is_active=True
467+
),
468+
self.assertPathExists(
469+
(interfaces[15], cable1, interfaces[7]), is_complete=True, is_active=True
470+
),
471+
]
472+
self.assertEqual(CablePath.objects.count(), len(paths))
473+
474+
for i, (interface, path) in enumerate(zip(interfaces, paths)):
475+
interface.refresh_from_db()
476+
self.assertPathIsSet(interface, path)
477+
self.assertEqual(interface.cable_end, 'A' if i < 8 else 'B')
478+
self.assertEqual(interface.cable_position, (i % 8) + 1)
479+
480+
# Test SVG generation
481+
CableTraceSVG(interfaces[0]).render()
482+
483+
# Delete cable 1
484+
cable1.delete()
485+
486+
# Check that all CablePaths have been deleted
487+
self.assertEqual(CablePath.objects.count(), 0)
488+
375489
def test_202_single_path_via_pass_through_with_breakouts(self):
376490
"""
377491
[IF1] --C1-- [FP1] [RP1] --C2-- [IF3]

0 commit comments

Comments
 (0)