diff --git a/changes/311.fixed b/changes/311.fixed new file mode 100644 index 0000000..d459f9a --- /dev/null +++ b/changes/311.fixed @@ -0,0 +1 @@ +Fixed issue with Lags, VRFs and Untagged Vlans not being removed from interfaces \ No newline at end of file diff --git a/nautobot_device_onboarding/diffsync/adapters/sync_network_data_adapters.py b/nautobot_device_onboarding/diffsync/adapters/sync_network_data_adapters.py index 081b40b..86ab5f2 100644 --- a/nautobot_device_onboarding/diffsync/adapters/sync_network_data_adapters.py +++ b/nautobot_device_onboarding/diffsync/adapters/sync_network_data_adapters.py @@ -157,12 +157,13 @@ def load_tagged_vlans_to_interface(self): vlan_dict["name"] = vlan.name vlan_dict["id"] = str(vlan.vid) tagged_vlans.append(vlan_dict) + sorted_tagged_vlans = sorted(tagged_vlans, key=lambda x: x["id"]) network_tagged_vlans_to_interface = self.tagged_vlans_to_interface( adapter=self, device__name=interface.device.name, name=interface.name, - tagged_vlans=tagged_vlans, + tagged_vlans=sorted_tagged_vlans, ) network_tagged_vlans_to_interface.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST self.add(network_tagged_vlans_to_interface) @@ -606,24 +607,26 @@ def load_vlans(self): if interface_data["untagged_vlan"] and interface_data["untagged_vlan"].get("id") == "0": self.job.logger.warning("Interface with untagged vlan 0 found. Skipping untagged vlan load.") continue - try: - network_vlan = self.vlan( - adapter=self, - name=interface_data["untagged_vlan"]["name"], - vid=interface_data["untagged_vlan"]["id"], - location__name=location_names.get(hostname, ""), - ) - self.add(network_vlan) - except diffsync.exceptions.ObjectAlreadyExists: - continue - except Exception as err: # pylint: disable=broad-exception-caught - self._handle_general_load_exception( - error=err, - hostname=hostname, - data=device_data, - model_type="vlan", - ) - continue + + if interface_data["untagged_vlan"]: + try: + network_vlan = self.vlan( + adapter=self, + name=interface_data["untagged_vlan"]["name"], + vid=interface_data["untagged_vlan"]["id"], + location__name=location_names.get(hostname, ""), + ) + self.add(network_vlan) + except diffsync.exceptions.ObjectAlreadyExists: + continue + except Exception as err: # pylint: disable=broad-exception-caught + self._handle_general_load_exception( + error=err, + hostname=hostname, + data=device_data, + model_type="vlan", + ) + continue def load_vrfs(self): """Load vrfs into the Diffsync store.""" @@ -691,11 +694,12 @@ def load_tagged_vlans_to_interface(self): # for interface in device_data["interfaces"]: for interface_name, interface_data in device_data["interfaces"].items(): try: + sorted_tagged_vlans = sorted(interface_data["tagged_vlans"], key=lambda x: x["id"]) network_tagged_vlans_to_interface = self.tagged_vlans_to_interface( adapter=self, device__name=hostname, name=interface_name, - tagged_vlans=interface_data["tagged_vlans"], + tagged_vlans=sorted_tagged_vlans, ) self.add(network_tagged_vlans_to_interface) except Exception as err: # pylint: disable=broad-exception-caught @@ -710,7 +714,6 @@ def load_tagged_vlans_to_interface(self): def load_untagged_vlan_to_interface(self): """Load untagged vlan to interface assignments into the Diffsync store.""" for hostname, device_data in self.job.command_getter_result.items(): - # for interface in device_data["interfaces"]: for interface_name, interface_data in device_data["interfaces"].items(): try: if interface_data["untagged_vlan"] and interface_data["untagged_vlan"].get("id") == "0": @@ -758,7 +761,6 @@ def load_lag_to_interface(self): def load_vrf_to_interface(self): """Load Vrf to interface assignments into the Diffsync store.""" for hostname, device_data in self.job.command_getter_result.items(): - # for interface in device_data["interfaces"]: for interface_name, interface_data in device_data["interfaces"].items(): try: network_vrf_to_interface = self.vrf_to_interface( diff --git a/nautobot_device_onboarding/diffsync/models/sync_network_data_models.py b/nautobot_device_onboarding/diffsync/models/sync_network_data_models.py index e8ecdf5..1d1f25a 100644 --- a/nautobot_device_onboarding/diffsync/models/sync_network_data_models.py +++ b/nautobot_device_onboarding/diffsync/models/sync_network_data_models.py @@ -249,21 +249,31 @@ class SyncNetworkDataTaggedVlansToInterface(DiffSyncModel): tagged_vlans: Optional[list] = None @classmethod - def _get_and_assign_tagged_vlans(cls, adapter, attrs, interface): - """Loop through the tagged vlans for an interface and assign them.""" - for network_vlan in attrs["tagged_vlans"]: - try: - nautobot_vlan = VLAN.objects.get( - name=network_vlan["name"], vid=network_vlan["id"], location=interface.device.location - ) - interface.tagged_vlans.add(nautobot_vlan) - except ObjectDoesNotExist: - adapter.job.logger.error( - f"Failed to assign tagged vlan to {interface.device}:{interface}, unable to locate a vlan " - f"with attributes [name: {network_vlan['name']}, vid: {network_vlan['id']} " - f"location: {interface.device.location}]" - ) - raise diffsync_exceptions.ObjectNotCreated + def _get_and_assign_tagged_vlan(cls, adapter, network_vlan, interface, diff_method_type): + """Assign a tagged vlan to an interface.""" + try: + nautobot_vlan = VLAN.objects.get( + name=network_vlan["name"], vid=network_vlan["id"], location=interface.device.location + ) + interface.tagged_vlans.add(nautobot_vlan) + except ObjectDoesNotExist as err: + adapter.job.logger.error( + f"Failed to assign tagged vlan to interface: [{interface}] on device: [{interface.device}]. " + f"Unable to locate a vlan with attributes [{network_vlan}] at location: [{interface.device.location}]" + ) + if diff_method_type == "create": + raise diffsync_exceptions.ObjectNotCreated(err) + if diff_method_type == "update": + raise diffsync_exceptions.ObjectNotUpdated(err) + except Exception as err: + adapter.job.logger.error( + f"Failed to assign tagged vlan: [{network_vlan}] " + f"to interface: [{interface}]on device: [{interface.device}], {err}" + ) + if diff_method_type == "create": + raise diffsync_exceptions.ObjectNotCreated(err) + if diff_method_type == "update": + raise diffsync_exceptions.ObjectNotUpdated(err) @classmethod def create(cls, adapter, ids, attrs): @@ -271,43 +281,56 @@ def create(cls, adapter, ids, attrs): if attrs.get("tagged_vlans"): try: interface = Interface.objects.get(device__name=ids["device__name"], name=ids["name"]) - except ObjectDoesNotExist: + except ObjectDoesNotExist as err: adapter.job.logger.error( - f"Failed to assign tagged vlans {attrs['tagged_vlans']}. An interface with " - f"attributes: [device__name: {ids['device__name']} name: {ids['name']}] was not found." + f"Failed to assign tagged vlans {attrs['tagged_vlans']}. " + f"An interface with identifiers: [{ids}] was not found." ) - raise diffsync_exceptions.ObjectNotCreated - cls._get_and_assign_tagged_vlans(adapter, attrs, interface) - if interface: - try: - interface.validated_save() - except ValidationError as err: - adapter.job.logger.error( - f"Failed to assign tagged vlans {attrs['tagged_vlans']} to {interface} on {interface.device}, {err}" - ) - raise diffsync_exceptions.ObjectNotCreated + raise diffsync_exceptions.ObjectNotCreated(err) + for network_vlan in attrs["tagged_vlans"]: + cls._get_and_assign_tagged_vlan(adapter, network_vlan, interface, diff_method_type="create") + try: + interface.validated_save() + except ValidationError as err: + adapter.job.logger.error( + f"Failed to assign tagged vlans {attrs['tagged_vlans']} to {interface} on {interface.device}, {err}" + ) + raise diffsync_exceptions.ObjectNotCreated(err) return super().create(adapter, ids, attrs) def update(self, attrs): """Update tagged vlans.""" + # An interface must exist before vlan assignments can be updated + try: + interface = Interface.objects.get(**self.get_identifiers()) + except ObjectDoesNotExist: + self.adapter.job.logger.error( + f"Failed to update tagged vlans, an interface with identifiers: [{self.get_identifiers()}] was not found." + ) + raise diffsync_exceptions.ObjectNotUpdated + # Clear all tagged vlans from an interface and assign them based on what was loaded into the diffsync store if attrs.get("tagged_vlans"): + interface.tagged_vlans.clear() + for network_vlan in attrs["tagged_vlans"]: + self._get_and_assign_tagged_vlan(self.adapter, network_vlan, interface, diff_method_type="update") try: - interface = Interface.objects.get(**self.get_identifiers()) - interface.tagged_vlans.clear() - except ObjectDoesNotExist: + interface.validated_save() + except ValidationError as err: self.adapter.job.logger.error( - f"Failed to assign tagged vlans {attrs['tagged_vlans']}. An interface with " - f"attributes: [{self.get_identifiers}] was not found." + f"Failed to assign tagged vlans {attrs['tagged_vlans']} " + f"to interface: [{interface}] on device: [{interface.device}], {err}" ) raise diffsync_exceptions.ObjectNotUpdated - self._get_and_assign_tagged_vlans(self.adapter, attrs, interface) + # Clear all tagged vlans from an interface + if not attrs.get("tagged_vlans"): + interface.tagged_vlans.clear() try: interface.validated_save() except ValidationError as err: self.adapter.job.logger.error( - f"Failed to assign tagged vlans {attrs['tagged_vlans']} to {interface} on {interface.device}, {err}" + f"Failed to remove tagged vlans from interface: [{interface}] on device: [{interface.device}], {err}" ) - raise diffsync_exceptions.ObjectNotUpdated + raise diffsync_exceptions.ObjectNotUpdated(err) return super().update(attrs) @@ -324,7 +347,7 @@ class SyncNetworkDataUnTaggedVlanToInterface(DiffSyncModel): untagged_vlan: Optional[dict] = None @classmethod - def _get_and_assign_untagged_vlan(cls, adapter, attrs, interface): + def _get_and_assign_untagged_vlan(cls, adapter, attrs, interface, diff_method_type): """Assign an untagged vlan to an interface.""" try: vlan = VLAN.objects.get( @@ -333,13 +356,25 @@ def _get_and_assign_untagged_vlan(cls, adapter, attrs, interface): location=interface.device.location, ) interface.untagged_vlan = vlan - except ObjectDoesNotExist: + except ObjectDoesNotExist as err: adapter.job.logger.error( - f"Failed to assign untagged vlan to {interface.device}:{interface}, unable to locate a vlan with " - f"attributes [name: {attrs['untagged_vlan']['name']}, vid: {attrs['untagged_vlan']['id']} " - f"location: {interface.device.location}]" + f"Failed to assign untagged vlan to interface: [{interface}] on device: [{interface.device}]. " + f"Unable to locate a vlan with attributes: [{attrs['untagged_vlan']}] " + f" at location: {interface.device.location}]" ) - raise diffsync_exceptions.ObjectNotCreated + if diff_method_type == "create": + raise diffsync_exceptions.ObjectNotCreated(err) + if diff_method_type == "update": + raise diffsync_exceptions.ObjectNotUpdated(err) + except Exception as err: + adapter.job.logger.error( + f"Failed to assign untagged vlan: [{attrs['untagged_vlan']}] " + f"to interface: [{interface}] on device: [{interface.device}], {err}" + ) + if diff_method_type == "create": + raise diffsync_exceptions.ObjectNotCreated(err) + if diff_method_type == "update": + raise diffsync_exceptions.ObjectNotUpdated(err) @classmethod def create(cls, adapter, ids, attrs): @@ -349,37 +384,50 @@ def create(cls, adapter, ids, attrs): interface = Interface.objects.get(device__name=ids["device__name"], name=ids["name"]) except ObjectDoesNotExist: adapter.job.logger.error( - f"Failed to assign untagged vlan {attrs['untagged_vlan']}. An interface with " - f"attributes: [device__name: {ids['device__name']} name: {ids['name']}] was not found." + f"Failed to assign untagged vlan {attrs['untagged_vlan']}. " + f"An interface with identifiers: [{ids}] was not found." ) raise diffsync_exceptions.ObjectNotCreated - cls._get_and_assign_untagged_vlan(adapter, attrs, interface) + cls._get_and_assign_untagged_vlan(adapter, attrs, interface, diff_method_type="create") try: interface.validated_save() except ValidationError as err: adapter.job.logger.error( - f"Failed to assign untagged vlan {attrs['untagged_vlan']} to {interface} on {interface.device}, {err}" + f"Failed to assign untagged vlan {attrs['untagged_vlan']} " + f"to interface: [{interface}] on device: [{interface.device}], {err}" ) - raise diffsync_exceptions.ObjectNotCreated + raise diffsync_exceptions.ObjectNotCreated(err) return super().create(adapter, ids, attrs) def update(self, attrs): """Update the untagged vlan on an interface.""" + # An interface must exist before vlan assignments can be updated + try: + interface = Interface.objects.get(**self.get_identifiers()) + except ObjectDoesNotExist: + self.adapter.job.logger.error( + f"Failed to update untagged vlan, an interface with identifiers: [{self.get_identifiers()}] was not found." + ) + raise diffsync_exceptions.ObjectNotUpdated + # Assign an untagged vlan to an interface if attrs.get("untagged_vlan"): + self._get_and_assign_untagged_vlan(self.adapter, attrs, interface, diff_method_type="update") try: - interface = Interface.objects.get(**self.get_identifiers()) - except ObjectDoesNotExist: + interface.validated_save() + except ValidationError as err: self.adapter.job.logger.error( - f"Failed to assign untagged vlan {attrs['untagged_vlan']}. An interface with " - f"attributes: [{self.get_identifiers}] was not found." + f"Failed to assign untagged vlan {attrs['untagged_vlan']} " + f"to interface: [{interface}] on device: [{interface.device}], {err}" ) raise diffsync_exceptions.ObjectNotUpdated - self._get_and_assign_untagged_vlan(self.adapter, attrs, interface) + # Removed an untagged vlan from an interface + if not attrs.get("untagged_vlan"): + interface.untagged_vlan = None try: interface.validated_save() except ValidationError as err: self.adapter.job.logger.error( - f"Failed to assign untagged vlans {attrs['untagged_vlan']} to {interface} on {interface.device}, {err}" + f"Failed to remove untagged vlan from {interface} on {interface.device}, {err}" ) raise diffsync_exceptions.ObjectNotUpdated return super().update(attrs) @@ -397,70 +445,79 @@ class SyncNetworkDataLagToInterface(DiffSyncModel): lag__interface__name: Optional[str] = None - # TODO: move the create and update method locgic to a single utility function + @classmethod + def _get_and_assign_lag(cls, adapter, attrs, interface, diff_method_type): + """Assign a lag interface to an interface.""" + try: + lag_interface = Interface.objects.get( + name=attrs["lag__interface__name"], device=interface.device, type=InterfaceTypeChoices.TYPE_LAG + ) + interface.lag = lag_interface + except ObjectDoesNotExist as err: + adapter.job.logger.error( + f"Failed to assign lag to interface: [{interface}] on device: [{interface.device}]. " + f"Unable to locate a lag interface with name: [{attrs['lag__interface__name']}] " + f"on device: [{interface.device}]" + ) + if diff_method_type == "create": + raise diffsync_exceptions.ObjectNotCreated(err) + if diff_method_type == "update": + raise diffsync_exceptions.ObjectNotUpdated(err) + @classmethod def create(cls, adapter, ids, attrs): """Assign a lag to an interface.""" if attrs["lag__interface__name"]: try: interface = Interface.objects.get(device__name=ids["device__name"], name=ids["name"]) - except ObjectDoesNotExist: + except ObjectDoesNotExist as err: adapter.job.logger.error( - f"Failed to assign lag {attrs['lag__interface__name']}. An interface with " - f"attributes: [device__name: {ids['device__name']} name: {ids['name']}] was not found." + f"Failed to assign lag: [{attrs['lag__interface__name']}]. " + f"An interface with identifiers: [{ids}] was not found." ) - raise diffsync_exceptions.ObjectNotCreated - if interface: - try: - lag_interface = Interface.objects.get( - name=attrs["lag__interface__name"], device=interface.device, type=InterfaceTypeChoices.TYPE_LAG - ) - interface.lag = lag_interface - interface.validated_save() - except ObjectDoesNotExist: - adapter.job.logger.error( - f"Failed to assign lag to {interface.device}:{interface}, unable to locate a lag interface " - f"with attributes [name: {attrs['lag__interface__name']}, device: {interface.device.name} " - f"type: {InterfaceTypeChoices.TYPE_LAG}]" - ) - raise diffsync_exceptions.ObjectNotCreated - except ValidationError as err: - adapter.job.logger.error( - f"Failed to assign lag {lag_interface} to {interface} on {interface.device}, {err}" - ) - raise diffsync_exceptions.ObjectNotCreated + raise diffsync_exceptions.ObjectNotCreated(err) + cls._get_and_assign_lag(adapter, attrs, interface, diff_method_type="create") + try: + interface.validated_save() + except ValidationError as err: + adapter.job.logger.error( + f"Failed to assign lag: [{attrs['lag__interface__name']}] " + f"to interface: [{interface}] on device: [{interface.device}], {err}" + ) + raise diffsync_exceptions.ObjectNotCreated(err) return super().create(adapter, ids, attrs) def update(self, attrs): """Update and interface lag.""" + # An interface must exist before lag can be updated + try: + interface = Interface.objects.get(**self.get_identifiers()) + except ObjectDoesNotExist as err: + self.adapter.job.logger.error( + f"Failed to update lag, an interface with identifiers: [{self.get_identifiers()}] was not found." + ) + raise diffsync_exceptions.ObjectNotUpdated(err) + # Assign lag to an interface if attrs.get("lag__interface__name"): + self._get_and_assign_lag(self.adapter, attrs, interface, diff_method_type="update") try: - interface = Interface.objects.get(**self.get_identifiers()) - except ObjectDoesNotExist: + interface.validated_save() + except ValidationError as err: self.adapter.job.logger.error( - f"Failed to assign untagged lag {attrs['lag__interface__name']}. " - f"An interface with attributes: [{self.get_identifiers}] was not found." + f"Failed to assign lag: [{attrs['lag__interface__name']}] " + f"to interface: [{interface}] on device: [{interface.device}], {err}" ) - raise diffsync_exceptions.ObjectNotUpdated - try: - lag_interface = Interface.objects.get( - name=attrs["lag__interface__name"], device=interface.device, type=InterfaceTypeChoices.TYPE_LAG - ) - interface.lag = lag_interface - interface.validated_save() - except ObjectDoesNotExist: - self.adapter.job.logger.error( - f"Failed to assign lag to {interface}, unable to locate a lag interface " - f"with attributes [name: {attrs['lag__interface__name']}, device: {interface.device.name} " - f"type: {InterfaceTypeChoices.TYPE_LAG}]" - ) - raise diffsync_exceptions.ObjectNotUpdated - except ValidationError as err: - self.adapter.job.logger.error( - f"Failed to assign lag {lag_interface} to {interface} on {interface.device}, {err}" - ) - raise diffsync_exceptions.ObjectNotUpdated - + raise diffsync_exceptions.ObjectNotUpdated(err) + # Remove lag from an interface + if not attrs.get("lag__interface__name"): + interface.lag = None + try: + interface.validated_save() + except ValidationError as err: + self.adapter.job.logger.error( + f"Failed to remove lag from interface: [{interface}] on device: [{interface.device}], {err}" + ) + raise diffsync_exceptions.ObjectNotUpdated(err) return super().update(attrs) @@ -488,35 +545,42 @@ class SyncNetworkDataVrfToInterface(DiffSyncModel): vrf: Optional[dict] = None @classmethod - def _get_and_assign_vrf(cls, adapter, attrs, interface): + def _get_and_assign_vrf(cls, adapter, attrs, interface, diff_method_type): """Assign a vrf to an interface.""" try: vrf = VRF.objects.get( name=attrs["vrf"]["name"], namespace=adapter.job.namespace, ) - except ObjectDoesNotExist: + except ObjectDoesNotExist as err: adapter.job.logger.error( - f"Failed to assign vrf to {interface.device}:{interface}, unable to locate a vrf with attributes " - f"[name: {attrs['vrf']['name']} " - f"namespace: {adapter.job.namespace}]" + f"Failed to assign vrf to interface: [{interface}] on device: [{interface.device}]. " + f"Unable to locate a vrf with name: [{attrs['vrf']['name']}] in namespace: [{adapter.job.namespace}]" ) - raise diffsync_exceptions.ObjectNotCreated - except MultipleObjectsReturned: + if diff_method_type == "create": + raise diffsync_exceptions.ObjectNotCreated(err) + if diff_method_type == "update": + raise diffsync_exceptions.ObjectNotUpdated(err) + except MultipleObjectsReturned as err: adapter.job.logger.error( - f"Failed to assign vrf to {interface.device}:{interface}, there are multipple vrfs with attributes " - f"[name: {attrs['vrf']['name']} " - f"namespace: {adapter.job.namespace}]. " + f"Failed to assign vrf to interface: [{interface}] on device: [{interface.device}]. " + f"There are multipple vrfs with name: [{attrs['vrf']['name']}] in namespace: [{adapter.job.namespace}]. " "Unsure which to assign." ) - raise diffsync_exceptions.ObjectNotCreated + if diff_method_type == "create": + raise diffsync_exceptions.ObjectNotCreated(err) + if diff_method_type == "update": + raise diffsync_exceptions.ObjectNotUpdated(err) try: vrf.devices.add(interface.device) vrf.validated_save() + interface.vrf = vrf except Exception as err: - adapter.logger.error(f"Failed to assign device: {interface.device} to vrf: {vrf}, {err}") - raise diffsync_exceptions.ObjectNotCreated - interface.vrf = vrf + adapter.logger.error(f"Failed to assign device: [{interface.device}] to vrf: [{vrf}], {err}") + if diff_method_type == "create": + raise diffsync_exceptions.ObjectNotCreated(err) + if diff_method_type == "update": + raise diffsync_exceptions.ObjectNotUpdated(err) @classmethod def create(cls, adapter, ids, attrs): @@ -524,41 +588,53 @@ def create(cls, adapter, ids, attrs): if attrs.get("vrf"): try: interface = Interface.objects.get(device__name=ids["device__name"], name=ids["name"]) - except ObjectDoesNotExist: + except ObjectDoesNotExist as err: adapter.job.logger.error( - f"Failed to assign vrf {attrs['vrf']}. An interface with attributes: " - f"[device__name: {ids['device__name']} name: {ids['name']}] was not found." + f"Failed to assign vrf: [{attrs['vrf']['name']}]. " + f"An interface with identifiers: [{ids}] was not found." ) - raise diffsync_exceptions.ObjectNotCreated - cls._get_and_assign_vrf(adapter, attrs, interface) + raise diffsync_exceptions.ObjectNotCreated(err) + cls._get_and_assign_vrf(adapter, attrs, interface, diff_method_type="create") try: interface.validated_save() except ValidationError as err: adapter.job.logger.error( - f"Failed to assign vrf {attrs['vrf']} to {interface} on {interface.device}, {err}" + f"Failed to assign vrf: [{attrs['vrf']}] " + f"to interface: [{interface}] on device: [{interface.device}], {err}" ) - raise diffsync_exceptions.ObjectNotCreated + raise diffsync_exceptions.ObjectNotCreated(err) return super().create(adapter, ids, attrs) def update(self, attrs): """Update the vrf on an interface.""" + # An interface must exist before vrf can be updated + try: + interface = Interface.objects.get(**self.get_identifiers()) + except ObjectDoesNotExist as err: + self.adapter.job.logger.error( + f"Failed to update vrf, an interface with identifiers: [{self.get_identifiers()}] was not found." + ) + raise diffsync_exceptions.ObjectNotUpdated(err) if attrs.get("vrf"): + # Assign a vrf to an interface + self._get_and_assign_vrf(self.adapter, attrs, interface, diff_method_type="update") try: - interface = Interface.objects.get(**self.get_identifiers()) - except ObjectDoesNotExist: + interface.validated_save() + except ValidationError as err: self.adapter.job.logger.error( - f"Failed to assign vrf {attrs['vrf']['name']}. " - f"An interface with attributes: [{self.get_identifiers}] was not found." + f"Failed to assign vrf: [{attrs['vrf']}] " + f"to interface: [{interface}] on device: [{interface.device}], {err}" ) - raise diffsync_exceptions.ObjectNotUpdated - self._get_and_assign_vrf(self.adapter, attrs, interface) + raise diffsync_exceptions.ObjectNotUpdated(err) + if not attrs.get("vrf"): + interface.vrf = None try: interface.validated_save() except ValidationError as err: self.adapter.job.logger.error( - f"Failed to assign vrf {attrs['vrf']} to {interface} on {interface.device}, {err}" + f"Failed to remove vrf from interface: [{interface}] on device: [{interface.device}], {err}" ) - raise diffsync_exceptions.ObjectNotUpdated + raise diffsync_exceptions.ObjectNotUpdated(err) return super().update(attrs)