Skip to content

Commit 27992ca

Browse files
authored
Fix bug that creates duplicate interfaces when syncing interfaces installed in a module (#476)
1 parent 209bcbb commit 27992ca

8 files changed

Lines changed: 199 additions & 154 deletions

File tree

changes/476.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed interfaces attached to modules being recreated when running the Sync Network Data job.

nautobot_device_onboarding/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,6 @@
5454

5555
# The git repository data source folder name for custom command mappers.
5656
ONBOARDING_COMMAND_MAPPERS_REPOSITORY_FOLDER = "onboarding_command_mappers"
57+
58+
# Support 4 modules deep (device -> modulebay -> module -> modulebay -> module -> modulebay -> module -> modulebay -> module -> interface)
59+
ONBOARDING_DEVICE_MODULE_RECURSION_LIMIT = 4

nautobot_device_onboarding/diffsync/adapters/sync_devices_adapters.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,18 +107,14 @@ def load_devices(self):
107107
self.job.logger.debug("Loading Device data from Nautobot...")
108108

109109
for device in Device.objects.filter(primary_ip4__host__in=list(self.job.ip_address_inventory)):
110-
interface_list = []
110+
interfaces = []
111111
# Only interfaces with the device's primary ip should be considered for diff calculations
112112
# Ultimately, only the first matching interface is used but this list could support multiple
113113
# interface syncs in the future.
114-
for interface in device.interfaces.all():
114+
for interface in device.all_interfaces.order_by("name"):
115115
if device.primary_ip4 in interface.ip_addresses.all():
116-
interface_list.append(interface.name)
117-
if interface_list:
118-
interface_list.sort()
119-
interfaces = [interface_list[0]]
120-
else:
121-
interfaces = []
116+
interfaces = [interface.name]
117+
break
122118
onboarding_device = self.device(
123119
adapter=self,
124120
pk=device.pk,

nautobot_device_onboarding/diffsync/adapters/sync_network_data_adapters.py

Lines changed: 71 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -167,60 +167,63 @@ def load_tagged_vlans_to_interface(self):
167167
168168
Only Vlan assignments that were returned by the CommandGetter job should be loaded.
169169
"""
170-
for interface in Interface.objects.filter(device__in=self.job.devices_to_load):
171-
tagged_vlans = []
172-
for vlan in interface.tagged_vlans.all():
173-
vlan_dict = {}
174-
vlan_dict["name"] = vlan.name
175-
vlan_dict["id"] = str(vlan.vid)
176-
tagged_vlans.append(vlan_dict)
177-
sorted_tagged_vlans = sorted(tagged_vlans, key=lambda x: x["id"])
178-
179-
network_tagged_vlans_to_interface = self.tagged_vlans_to_interface(
180-
adapter=self,
181-
device__name=interface.device.name,
182-
name=interface.name,
183-
tagged_vlans=sorted_tagged_vlans,
184-
)
185-
network_tagged_vlans_to_interface.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST
186-
self.add(network_tagged_vlans_to_interface)
170+
for device in self.job.devices_to_load:
171+
for interface in device.all_interfaces:
172+
tagged_vlans = []
173+
for vlan in interface.tagged_vlans.all():
174+
vlan_dict = {}
175+
vlan_dict["name"] = vlan.name
176+
vlan_dict["id"] = str(vlan.vid)
177+
tagged_vlans.append(vlan_dict)
178+
sorted_tagged_vlans = sorted(tagged_vlans, key=lambda x: x["id"])
179+
180+
network_tagged_vlans_to_interface = self.tagged_vlans_to_interface(
181+
adapter=self,
182+
device__name=device.name,
183+
name=interface.name,
184+
tagged_vlans=sorted_tagged_vlans,
185+
)
186+
network_tagged_vlans_to_interface.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST
187+
self.add(network_tagged_vlans_to_interface)
187188

188189
def load_untagged_vlan_to_interface(self):
189190
"""
190191
Load UnTagged VLAN interface assignments into the Diffsync store.
191192
192193
Only UnTagged Vlan assignments that were returned by the CommandGetter job should be synced.
193194
"""
194-
for interface in Interface.objects.filter(device__in=self.job.devices_to_load):
195-
untagged_vlan = {}
196-
if interface.untagged_vlan:
197-
untagged_vlan["name"] = interface.untagged_vlan.name
198-
untagged_vlan["id"] = str(interface.untagged_vlan.vid)
195+
for device in self.job.devices_to_load:
196+
for interface in device.all_interfaces:
197+
untagged_vlan = {}
198+
if interface.untagged_vlan:
199+
untagged_vlan["name"] = interface.untagged_vlan.name
200+
untagged_vlan["id"] = str(interface.untagged_vlan.vid)
199201

200-
network_untagged_vlan_to_interface = self.untagged_vlan_to_interface(
201-
adapter=self,
202-
device__name=interface.device.name,
203-
name=interface.name,
204-
untagged_vlan=untagged_vlan,
205-
)
206-
network_untagged_vlan_to_interface.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST
207-
self.add(network_untagged_vlan_to_interface)
202+
network_untagged_vlan_to_interface = self.untagged_vlan_to_interface(
203+
adapter=self,
204+
device__name=device.name,
205+
name=interface.name,
206+
untagged_vlan=untagged_vlan,
207+
)
208+
network_untagged_vlan_to_interface.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST
209+
self.add(network_untagged_vlan_to_interface)
208210

209211
def load_lag_to_interface(self):
210212
"""
211213
Load Lag interface assignments into the Diffsync store.
212214
213215
Only Lag assignments that were returned by the CommandGetter job should be synced.
214216
"""
215-
for interface in Interface.objects.filter(device__in=self.job.devices_to_load):
216-
network_lag_to_interface = self.lag_to_interface(
217-
adapter=self,
218-
device__name=interface.device.name,
219-
name=interface.name,
220-
lag__interface__name=interface.lag.name if interface.lag else "",
221-
)
222-
network_lag_to_interface.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST
223-
self.add(network_lag_to_interface)
217+
for device in self.job.devices_to_load:
218+
for interface in device.all_interfaces:
219+
network_lag_to_interface = self.lag_to_interface(
220+
adapter=self,
221+
device__name=device.name,
222+
name=interface.name,
223+
lag__interface__name=interface.lag.name if interface.lag else "",
224+
)
225+
network_lag_to_interface.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST
226+
self.add(network_lag_to_interface)
224227

225228
def load_vrfs(self):
226229
"""
@@ -246,19 +249,20 @@ def load_vrf_to_interface(self):
246249
247250
Only Vrf assignments that were returned by the CommandGetter job should be synced.
248251
"""
249-
for interface in Interface.objects.filter(device__in=self.job.devices_to_load):
250-
vrf = {}
251-
if interface.vrf:
252-
vrf["name"] = interface.vrf.name
252+
for device in self.job.devices_to_load:
253+
for interface in device.all_interfaces:
254+
vrf = {}
255+
if interface.vrf:
256+
vrf["name"] = interface.vrf.name
253257

254-
network_vrf_to_interface = self.vrf_to_interface(
255-
adapter=self,
256-
device__name=interface.device.name,
257-
name=interface.name,
258-
vrf=vrf,
259-
)
260-
network_vrf_to_interface.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST
261-
self.add(network_vrf_to_interface)
258+
network_vrf_to_interface = self.vrf_to_interface(
259+
adapter=self,
260+
device__name=device.name,
261+
name=interface.name,
262+
vrf=vrf,
263+
)
264+
network_vrf_to_interface.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST
265+
self.add(network_vrf_to_interface)
262266

263267
def load_cables(self):
264268
"""
@@ -345,6 +349,21 @@ def load_software_version_to_device(self):
345349
network_software_version_to_device.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST
346350
self.add(network_software_version_to_device)
347351

352+
def _handle_single_parameter(self, parameters, parameter_name, database_object, diffsync_model):
353+
"""Overload parameter handling to add special handling for modular interfaces."""
354+
if parameter_name == "device__name":
355+
if database_object.parent:
356+
parameters["device__name"] = database_object.parent.name
357+
else:
358+
parameters["device__name"] = ""
359+
elif parameter_name == "interface__device__name":
360+
if database_object.interface.parent:
361+
parameters["interface__device__name"] = database_object.interface.parent.name
362+
else:
363+
parameters["interface__device__name"] = ""
364+
else:
365+
super()._handle_single_parameter(parameters, parameter_name, database_object, diffsync_model)
366+
348367
def load(self):
349368
"""Generic implementation of the load function."""
350369
if not hasattr(self, "top_level") or not self.top_level:
@@ -414,7 +433,7 @@ def sync_complete(self, source, diff, *args, **kwargs):
414433
)
415434
if ip_address:
416435
try:
417-
interface = Interface.objects.get(device=device, ip_addresses__in=[ip_address])
436+
interface = device.all_interfaces.get(ip_addresses__in=[ip_address])
418437
interface.mgmt_only = True
419438
interface.validated_save()
420439
self.job.logger.info(

nautobot_device_onboarding/diffsync/models/sync_devices_models.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,7 @@ def _get_or_create_interface(cls, adapter, device, ip_address, interface_name):
100100
"""Attempt to get a Device Interface, create a new one if necessary."""
101101
device_interface = None
102102
try:
103-
device_interface = Interface.objects.get(
104-
name=interface_name,
105-
device=device,
106-
)
103+
device_interface = device.all_interfaces.get(name=interface_name)
107104
except ObjectDoesNotExist:
108105
try:
109106
job_form_attrs = adapter.job.ip_address_inventory[ip_address]
@@ -156,10 +153,7 @@ def _update_device_with_attrs(cls, device, platform, ids, attrs, adapter):
156153
def _remove_old_interface_assignment(self, device, ip_address):
157154
"""Remove a device's primary IP address from an interface."""
158155
try:
159-
old_interface = Interface.objects.get(
160-
device=device,
161-
ip_addresses__in=[ip_address],
162-
)
156+
old_interface = device.all_interfaces.get(ip_addresses__in=[ip_address])
163157
old_interface_assignment = IPAddressToInterface.objects.get(
164158
interface=old_interface,
165159
ip_address=ip_address,

0 commit comments

Comments
 (0)