nautobot-app-ssot icon indicating copy to clipboard operation
nautobot-app-ssot copied to clipboard

VLAN locations are not synchronized

Open steven-douilliet opened this issue 2 months ago • 1 comments

Environment

  • Python version: 3.9.19
  • Nautobot version: 2.2.0
  • nautobot-ssot version: 2.5.0

Expected Behavior

I expected VLAN synchronization from Infoblox to Nautobot, including:

  • Location
  • VLAN group
  • VLAN ID
  • VLAN name
  • Tenant
  • Status
  • Role
  • Description

During synchronization, VLANs should be automatically updated or created with the above attributes.

Except for the VLAN ID and VLAN name, all other attributes are set using extended attributes from Infoblox.

Observed Behavior

VLANs are updated or created, and all attributes are set except for the location. Locations already exist in Nautobot but are not bound to VLANs.

Steps to Reproduce

  1. Create the location from Nautobot.
  2. Create a VLAN on Infoblox.
  3. Set the Location extensible attribute and other attributes.
  4. Run the sync job.

The VLAN will be created based on Infoblox data, but its locations will not be set.

Cause

The root cause is the diffsync model of Nautobot.

Completing the location_id attribute does not associate the location with the VLAN. The VLAN model has a locations field, which is a many-to-many field, and it is here that the location must be set.

Workaround

Create a generic function to process VLAN locations:

def process_vlan_location(diffsync, obj: object, extattrs: dict):
    for attr, attr_value in extattrs.items():  # pylint: disable=too-many-nested-blocks
        if attr_value:
            if attr.lower() in ["site", "facility", "location"]:
                try:
                    location = Location.objects.get(
                        id=diffsync.location_map[attr_value]
                    )
                    obj.locations.add(location)
                except KeyError as err:
                    diffsync.job.logger.warning(
                        f"Unable to find Location {attr_value} for {obj} found in Extensibility Attributes '{attr}'. {err}"
                    )
                except TypeError as err:
                    diffsync.job.logger.warning(
                        f"Cannot set location values {attr_value} for {obj}. Multiple locations are assigned "
                        f"in Extensibility Attributes '{attr}', but multiple location assignments are not "
                        f"supported by Nautobot. {err}"
                    )
                except Exception as err:
                    diffsync.job.logger.warning(
                        f"An error has occured in the after save ! {err}"
                    )

Updated created and updated methods of the NautobotVlan class

class NautobotVlan(Vlan):
    """Nautobot implementation of the Vlan model."""

    @classmethod
    def create(cls, diffsync, ids, attrs):
        """Create VLAN object in Nautobot."""
        _vlan = OrmVlan(
            vid=ids["vid"],
            name=ids["name"],
            status_id=diffsync.status_map[cls.get_vlan_status(attrs["status"])],
            vlan_group_id=diffsync.vlangroup_map[ids["vlangroup"]],
            description=attrs["description"],
        )
        if "ext_attrs" in attrs:
            process_ext_attrs(diffsync=diffsync, obj=_vlan, extattrs=attrs["ext_attrs"])
        try:
            _vlan.validated_save()
            # FIX : Many to Many LocationVLANAssignments
            if "ext_attrs" in attrs:
                process_vlan_location(
                    diffsync=diffsync,
                    obj=_vlan, extattrs=attrs["ext_attrs"]
                )
            if ids["vlangroup"] not in diffsync.vlan_map:
                diffsync.vlan_map[ids["vlangroup"]] = {}
            diffsync.vlan_map[ids["vlangroup"]][_vlan.vid] = _vlan.id
            return super().create(ids=ids, diffsync=diffsync, attrs=attrs)
        except ValidationError as err:
            diffsync.job.logger.warning(f"Unable to create VLAN {ids['name']} {ids['vid']}. {err}")
            return None

    @staticmethod
    def get_vlan_status(status: str) -> str:
        """Return VLAN Status from mapping."""
        statuses = {
            "ASSIGNED": "Active",
            "UNASSIGNED": "Deprecated",
            "RESERVED": "Reserved",
        }
        return statuses[status]

    def update(self, attrs):
        """Update VLAN object in Nautobot."""
        _vlan = OrmVlan.objects.get(id=self.pk)
        if attrs.get("status"):
            _vlan.status_id = self.diffsync.status_map[self.get_vlan_status(attrs["status"])]
        if attrs.get("description"):
            _vlan.description = attrs["description"]
        if "ext_attrs" in attrs:
            process_ext_attrs(diffsync=self.diffsync, obj=_vlan, extattrs=attrs["ext_attrs"])
            # FIX : Many to Many LocationVLANAssignments
            if "location" in attrs["ext_attrs"]:
                _vlan.locations.clear()
                process_vlan_location(
                    diffsync=self.diffsync,
                    obj=_vlan, extattrs=attrs["ext_attrs"]
                )
        if not _vlan.vlan_group.location and _vlan.location:
            _vlan.vlan_group.location = _vlan.location
            _vlan.vlan_group.validated_save()
        try:
            _vlan.validated_save()
        except ValidationError as err:
            self.diffsync.job.logger.warning(f"Unable to update VLAN {_vlan.name} {_vlan.vid}. {err}")
            return None
        return super().update(attrs)

steven-douilliet avatar Apr 16 '24 12:04 steven-douilliet