Skip to content

Commit 359179f

Browse files
authored
fix(dcim): Add port mapping creation for module install (#21308)
1 parent c44e860 commit 359179f

File tree

3 files changed

+142
-4
lines changed

3 files changed

+142
-4
lines changed

netbox/dcim/models/modules.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from jsonschema.exceptions import ValidationError as JSONValidationError
88

99
from dcim.choices import *
10-
from dcim.utils import update_interface_bridges
10+
from dcim.utils import create_port_mappings, update_interface_bridges
1111
from extras.models import ConfigContextModel, CustomField
1212
from netbox.models import PrimaryModel
1313
from netbox.models.features import ImageAttachmentsMixin
@@ -361,5 +361,7 @@ def save(self, *args, **kwargs):
361361
update_fields=update_fields
362362
)
363363

364+
# Replicate any front/rear port mappings from the ModuleType
365+
create_port_mappings(self.device, self.module_type, self)
364366
# Interface bridges have to be set after interface instantiation
365367
update_interface_bridges(self.device, self.module_type.interfacetemplates, self)

netbox/dcim/tests/test_models.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,142 @@ def test_module_bay_parent_cleared_when_module_removed(self):
875875
self.assertIsNone(bay2.parent)
876876
self.assertIsNone(bay2.module)
877877

878+
def test_module_installation_creates_port_mappings(self):
879+
"""
880+
Test that installing a module with front/rear port templates correctly
881+
creates PortMapping instances for the device.
882+
"""
883+
device = Device.objects.first()
884+
manufacturer = Manufacturer.objects.first()
885+
module_bay = ModuleBay.objects.create(device=device, name='Test Bay PortMapping 1')
886+
887+
# Create a module type with a rear port template
888+
module_type_with_mappings = ModuleType.objects.create(
889+
manufacturer=manufacturer,
890+
model='Module Type With Mappings',
891+
)
892+
893+
# Create a rear port template with 12 positions (splice)
894+
rear_port_template = RearPortTemplate.objects.create(
895+
module_type=module_type_with_mappings,
896+
name='Rear Port 1',
897+
type=PortTypeChoices.TYPE_SPLICE,
898+
positions=12,
899+
)
900+
901+
# Create 12 front port templates mapped to the rear port
902+
front_port_templates = []
903+
for i in range(1, 13):
904+
front_port_template = FrontPortTemplate.objects.create(
905+
module_type=module_type_with_mappings,
906+
name=f'port {i}',
907+
type=PortTypeChoices.TYPE_LC,
908+
positions=1,
909+
)
910+
front_port_templates.append(front_port_template)
911+
912+
# Create port template mapping
913+
PortTemplateMapping.objects.create(
914+
device_type=None,
915+
module_type=module_type_with_mappings,
916+
front_port=front_port_template,
917+
front_port_position=1,
918+
rear_port=rear_port_template,
919+
rear_port_position=i,
920+
)
921+
922+
# Install the module
923+
module = Module.objects.create(
924+
device=device,
925+
module_bay=module_bay,
926+
module_type=module_type_with_mappings,
927+
status=ModuleStatusChoices.STATUS_ACTIVE,
928+
)
929+
930+
# Verify that front ports were created
931+
front_ports = FrontPort.objects.filter(device=device, module=module)
932+
self.assertEqual(front_ports.count(), 12)
933+
934+
# Verify that the rear port was created
935+
rear_ports = RearPort.objects.filter(device=device, module=module)
936+
self.assertEqual(rear_ports.count(), 1)
937+
rear_port = rear_ports.first()
938+
self.assertEqual(rear_port.positions, 12)
939+
940+
# Verify that port mappings were created
941+
port_mappings = PortMapping.objects.filter(front_port__module=module)
942+
self.assertEqual(port_mappings.count(), 12)
943+
944+
# Verify each mapping is correct
945+
for i, front_port_template in enumerate(front_port_templates, start=1):
946+
front_port = FrontPort.objects.get(
947+
device=device,
948+
name=front_port_template.name,
949+
module=module,
950+
)
951+
952+
# Check that a mapping exists for this front port
953+
mapping = PortMapping.objects.get(
954+
device=device,
955+
front_port=front_port,
956+
front_port_position=1,
957+
)
958+
959+
self.assertEqual(mapping.rear_port, rear_port)
960+
self.assertEqual(mapping.front_port_position, 1)
961+
self.assertEqual(mapping.rear_port_position, i)
962+
963+
def test_module_installation_without_mappings(self):
964+
"""
965+
Test that installing a module without port template mappings
966+
doesn't create any PortMapping instances.
967+
"""
968+
device = Device.objects.first()
969+
manufacturer = Manufacturer.objects.first()
970+
module_bay = ModuleBay.objects.create(device=device, name='Test Bay PortMapping 2')
971+
972+
# Create a module type without any port template mappings
973+
module_type_no_mappings = ModuleType.objects.create(
974+
manufacturer=manufacturer,
975+
model='Module Type Without Mappings',
976+
)
977+
978+
# Create a rear port template
979+
RearPortTemplate.objects.create(
980+
module_type=module_type_no_mappings,
981+
name='Rear Port 1',
982+
type=PortTypeChoices.TYPE_SPLICE,
983+
positions=12,
984+
)
985+
986+
# Create front port templates but DO NOT create PortTemplateMapping rows
987+
for i in range(1, 13):
988+
FrontPortTemplate.objects.create(
989+
module_type=module_type_no_mappings,
990+
name=f'port {i}',
991+
type=PortTypeChoices.TYPE_LC,
992+
positions=1,
993+
)
994+
995+
# Install the module
996+
module = Module.objects.create(
997+
device=device,
998+
module_bay=module_bay,
999+
module_type=module_type_no_mappings,
1000+
status=ModuleStatusChoices.STATUS_ACTIVE,
1001+
)
1002+
1003+
# Verify no port mappings were created for this module
1004+
port_mappings = PortMapping.objects.filter(
1005+
device=device,
1006+
front_port__module=module,
1007+
front_port_position=1,
1008+
)
1009+
self.assertEqual(port_mappings.count(), 0)
1010+
self.assertEqual(FrontPort.objects.filter(module=module).count(), 12)
1011+
self.assertEqual(RearPort.objects.filter(module=module).count(), 1)
1012+
self.assertEqual(PortMapping.objects.filter(front_port__module=module).count(), 0)
1013+
8781014

8791015
class CableTestCase(TestCase):
8801016

netbox/dcim/utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,13 @@ def update_interface_bridges(device, interface_templates, module=None):
8585
interface.save()
8686

8787

88-
def create_port_mappings(device, device_type, module=None):
88+
def create_port_mappings(device, device_or_module_type, module=None):
8989
"""
90-
Replicate all front/rear port mappings from a DeviceType to the given device.
90+
Replicate all front/rear port mappings from a DeviceType or ModuleType to the given device.
9191
"""
9292
from dcim.models import FrontPort, PortMapping, RearPort
9393

94-
templates = device_type.port_mappings.prefetch_related('front_port', 'rear_port')
94+
templates = device_or_module_type.port_mappings.prefetch_related('front_port', 'rear_port')
9595

9696
# Cache front & rear ports for efficient lookups by name
9797
front_ports = {

0 commit comments

Comments
 (0)