Skip to content

Commit

Permalink
Merge pull request #410 from progala/progala-fix-infoblox-tags-and-cfs
Browse files Browse the repository at this point in the history
Fix tagging and custom field updates for objects synced to/from Infoblox.
  • Loading branch information
Kircheneer authored Apr 4, 2024
2 parents 10e38c9 + 9167091 commit 6e4c42b
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 20 deletions.
1 change: 1 addition & 0 deletions changes/409.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
+ Fixes tagging and custom field updates for Nautobot objects synced to/from Infoblox.
23 changes: 11 additions & 12 deletions nautobot_ssot/integrations/infoblox/diffsync/adapters/nautobot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Nautobot Adapter for Infoblox integration."""
# pylint: disable=duplicate-code
from collections import defaultdict
import datetime
from diffsync import DiffSync
from diffsync.exceptions import ObjectAlreadyExists, ObjectNotFound
Expand All @@ -18,7 +17,6 @@
)
from nautobot_ssot.integrations.infoblox.constant import TAG_COLOR
from nautobot_ssot.integrations.infoblox.utils.diffsync import (
create_tag_sync_from_infoblox,
nautobot_vlan_status,
get_default_custom_fields,
)
Expand All @@ -40,18 +38,20 @@ def tag_involved_objects(self, target):
"color": TAG_COLOR,
},
)
for model in [IPAddress, Prefix, VLAN]:
tag.content_types.add(ContentType.objects.get_for_model(model))
# Ensure that the "ssot_synced_to_infoblox" custom field is present; as above, it *should* already exist.
custom_field, _ = CustomField.objects.get_or_create(
type=CustomFieldTypeChoices.TYPE_DATE,
name="ssot_synced_to_infoblox",
key="ssot_synced_to_infoblox",
defaults={
"label": "Last synced to Infoblox on",
},
)
for model in [IPAddress, Prefix]:
for model in [IPAddress, Prefix, VLAN, VLANGroup]:
custom_field.content_types.add(ContentType.objects.get_for_model(model))

for modelname in ["ipaddress", "prefix"]:
for modelname in ["ipaddress", "prefix", "vlan", "vlangroup"]:
for local_instance in self.get_all(modelname):
unique_id = local_instance.get_unique_id()
# Verify that the object now has a counterpart in the target DiffSync
Expand All @@ -72,13 +72,17 @@ def _tag_object(nautobot_object):
if hasattr(nautobot_object, "tags"):
nautobot_object.tags.add(tag)
if hasattr(nautobot_object, "cf"):
nautobot_object.cf[custom_field.name] = today
nautobot_object.cf[custom_field.key] = today
nautobot_object.validated_save()

if modelname == "ipaddress":
_tag_object(IPAddress.objects.get(pk=model_instance.pk))
elif modelname == "prefix":
_tag_object(Prefix.objects.get(pk=model_instance.pk))
elif modelname == "vlan":
_tag_object(VLAN.objects.get(pk=model_instance.pk))
elif modelname == "vlangroup":
_tag_object(VLANGroup.objects.get(pk=model_instance.pk))


class NautobotAdapter(NautobotMixin, DiffSync): # pylint: disable=too-many-instance-attributes
Expand Down Expand Up @@ -112,19 +116,14 @@ def __init__(self, *args, job=None, sync=None, **kwargs):
super().__init__(*args, **kwargs)
self.job = job
self.sync = sync
self.objects_to_create = defaultdict(list)

def sync_complete(self, source: DiffSync, *args, **kwargs):
"""Process object creations/updates using bulk operations.
Args:
source (DiffSync): Source DiffSync adapter data.
"""
for obj_type, objs in self.objects_to_create.items():
if obj_type != "vlangroups":
self.job.logger.info(f"Adding tags to all imported {obj_type}.")
for obj in objs:
obj.tags.add(create_tag_sync_from_infoblox())
super().sync_complete(source, *args, **kwargs)

def load_prefixes(self):
"""Load Prefixes from Nautobot."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from nautobot.ipam.models import VLANGroup as OrmVlanGroup
from nautobot_ssot.integrations.infoblox.constant import PLUGIN_CFG
from nautobot_ssot.integrations.infoblox.diffsync.models.base import Network, IPAddress, Vlan, VlanView
from nautobot_ssot.integrations.infoblox.utils.diffsync import create_tag_sync_from_infoblox
from nautobot_ssot.integrations.infoblox.utils.nautobot import get_prefix_vlans


Expand Down Expand Up @@ -132,6 +133,7 @@ def create(cls, diffsync, ids, attrs):

if attrs.get("ext_attrs"):
process_ext_attrs(diffsync=diffsync, obj=_prefix, extattrs=attrs["ext_attrs"])
_prefix.tags.add(create_tag_sync_from_infoblox())
_prefix.validated_save()
diffsync.prefix_map[ids["network"]] = _prefix.id
return super().create(ids=ids, diffsync=diffsync, attrs=attrs)
Expand Down Expand Up @@ -226,6 +228,7 @@ def create(cls, diffsync, ids, attrs):
if attrs.get("ext_attrs"):
process_ext_attrs(diffsync=diffsync, obj=_ip, extattrs=attrs["ext_attrs"])
try:
_ip.tags.add(create_tag_sync_from_infoblox())
_ip.validated_save()
diffsync.ipaddr_map[_ip.address] = _ip.id
return super().create(ids=ids, diffsync=diffsync, attrs=attrs)
Expand Down Expand Up @@ -317,6 +320,7 @@ def create(cls, diffsync, ids, attrs):
if "ext_attrs" in attrs:
process_ext_attrs(diffsync=diffsync, obj=_vlan, extattrs=attrs["ext_attrs"])
try:
_vlan.tags.add(create_tag_sync_from_infoblox())
_vlan.validated_save()
if ids["vlangroup"] not in diffsync.vlan_map:
diffsync.vlan_map[ids["vlangroup"]] = {}
Expand Down
18 changes: 10 additions & 8 deletions nautobot_ssot/integrations/infoblox/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def register_signals(sender):
nautobot_database_ready.connect(nautobot_database_ready_callback, sender=sender)


def nautobot_database_ready_callback(sender, *, apps, **kwargs): # pylint: disable=unused-argument
def nautobot_database_ready_callback(sender, *, apps, **kwargs): # pylint: disable=unused-argument,too-many-locals
"""Create Tag and CustomField to note System of Record for SSoT.
Callback function triggered by the nautobot_database_ready signal when the Nautobot database is fully ready.
Expand All @@ -24,35 +24,37 @@ def nautobot_database_ready_callback(sender, *, apps, **kwargs): # pylint: disa
Tag = apps.get_model("extras", "Tag")
Relationship = apps.get_model("extras", "Relationship")
VLAN = apps.get_model("ipam", "VLAN")
VLANGroup = apps.get_model("ipam", "VLANGroup")

Tag.objects.get_or_create(
tag_sync_from_infoblox, _ = Tag.objects.get_or_create(
name="SSoT Synced from Infoblox",
defaults={
"name": "SSoT Synced from Infoblox",
"description": "Object synced at some point from Infoblox",
"color": TAG_COLOR,
},
)
Tag.objects.get_or_create(
for model in [IPAddress, Prefix, VLAN]:
tag_sync_from_infoblox.content_types.add(ContentType.objects.get_for_model(model))
tag_sync_to_infoblox, _ = Tag.objects.get_or_create(
name="SSoT Synced to Infoblox",
defaults={
"name": "SSoT Synced to Infoblox",
"description": "Object synced at some point to Infoblox",
"color": TAG_COLOR,
},
)
for model in [IPAddress, Prefix, VLAN]:
tag_sync_to_infoblox.content_types.add(ContentType.objects.get_for_model(model))
custom_field, _ = CustomField.objects.get_or_create(
type=CustomFieldTypeChoices.TYPE_DATE,
key="ssot_synced_to_infoblox",
defaults={
"label": "Last synced to Infoblox on",
},
)
for content_type in [
ContentType.objects.get_for_model(Prefix),
ContentType.objects.get_for_model(IPAddress),
]:
custom_field.content_types.add(content_type)
for model in [IPAddress, Prefix, VLAN, VLANGroup]:
custom_field.content_types.add(ContentType.objects.get_for_model(model))
range_custom_field, _ = CustomField.objects.get_or_create(
type=CustomFieldTypeChoices.TYPE_TEXT,
key="dhcp_ranges",
Expand Down
3 changes: 3 additions & 0 deletions nautobot_ssot/integrations/infoblox/utils/diffsync.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Utilities for DiffSync related stuff."""
from django.contrib.contenttypes.models import ContentType
from django.utils.text import slugify
from nautobot.ipam.models import IPAddress, Prefix, VLAN
from nautobot.extras.models import CustomField, Tag
from nautobot_ssot.integrations.infoblox.constant import TAG_COLOR

Expand All @@ -15,6 +16,8 @@ def create_tag_sync_from_infoblox():
"color": TAG_COLOR,
},
)
for model in [IPAddress, Prefix, VLAN]:
tag.content_types.add(ContentType.objects.get_for_model(model))
return tag


Expand Down
193 changes: 193 additions & 0 deletions nautobot_ssot/tests/infoblox/test_tags_and_cfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
"""Tests covering use of tags and custom fields in the plugin."""
import datetime
from unittest.mock import Mock

from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from nautobot.extras.models import CustomField, Status, Tag
from nautobot.ipam.models import VLAN, IPAddress, Prefix, VLANGroup

from nautobot_ssot.integrations.infoblox.diffsync.adapters.infoblox import InfobloxAdapter
from nautobot_ssot.integrations.infoblox.diffsync.adapters.nautobot import NautobotAdapter


class TestTagging(TestCase):
"""Tests ensuring tagging is applied to objects synced from and to Infoblox."""

def setUp(self):
"Test class set up."
self.tag_sync_from_infoblox = Tag.objects.get(name="SSoT Synced from Infoblox")
self.tag_sync_to_infoblox = Tag.objects.get(name="SSoT Synced to Infoblox")

def test_tags_have_correct_content_types_set(self):
"""Ensure tags have correct content types configured."""
for model in (IPAddress, Prefix, VLAN):
content_type = ContentType.objects.get_for_model(model)
self.assertIn(content_type, self.tag_sync_from_infoblox.content_types.all())
self.assertIn(content_type, self.tag_sync_to_infoblox.content_types.all())

def test_objects_synced_from_infoblox_are_tagged(self):
"""Ensure objects synced from Infoblox have 'SSoT Synced from Infoblox' tag applied."""
nb_diffsync = NautobotAdapter()
nb_diffsync.job = Mock()
nb_diffsync.load()

infoblox_adapter = InfobloxAdapter(conn=Mock())

ds_prefix = infoblox_adapter.prefix(
network="10.0.0.0/8",
description="Test Network",
network_type="network",
status="Active",
)
infoblox_adapter.add(ds_prefix)
ds_ipaddress = infoblox_adapter.ipaddress(
description="Test IPAddress",
address="10.0.0.1",
status="Active",
dns_name="",
prefix="10.0.0.0/8",
prefix_length=8,
ip_addr_type="host",
)
infoblox_adapter.add(ds_ipaddress)
ds_vlangroup = infoblox_adapter.vlangroup(
name="TestVLANGroup",
description="",
)
infoblox_adapter.add(ds_vlangroup)
ds_vlan = infoblox_adapter.vlan(
vid=750,
name="TestVLAN",
description="Test VLAN",
status="ASSIGNED",
vlangroup="TestVLANGroup",
ext_attrs={},
)
infoblox_adapter.add(ds_vlan)

nb_diffsync.sync_from(infoblox_adapter)

prefix = Prefix.objects.get(network="10.0.0.0", prefix_length="8")
self.assertEqual(prefix.tags.all()[0], self.tag_sync_from_infoblox)

ipaddress = IPAddress.objects.get(address="10.0.0.1/8")
self.assertEqual(ipaddress.tags.all()[0], self.tag_sync_from_infoblox)

vlan = VLAN.objects.get(vid=750)
self.assertEqual(vlan.tags.all()[0], self.tag_sync_from_infoblox)

def test_objects_synced_to_infoblox_are_tagged(self):
"""Ensure objects synced to Infoblox have 'SSoT Synced to Infoblox' tag applied."""
nb_prefix = Prefix(
network="10.0.0.0",
prefix_length=8,
description="Test Network",
type="network",
status=Status.objects.get_for_model(Prefix).first(),
)
nb_prefix.validated_save()
nb_ipaddress = IPAddress(
description="Test IPAddress",
address="10.0.0.1/8",
status=Status.objects.get_for_model(IPAddress).first(),
type="host",
)
nb_ipaddress.validated_save()
nb_vlangroup = VLANGroup(
name="TestVLANGroup",
)
nb_vlangroup.validated_save()
nb_vlan = VLAN(
vid=750,
name="VL750",
description="Test VLAN",
status=Status.objects.get_for_model(VLAN).first(),
vlan_group=nb_vlangroup,
)
nb_vlan.validated_save()

nautobot_adapter = NautobotAdapter()
nautobot_adapter.job = Mock()
nautobot_adapter.load()

infoblox_adapter = InfobloxAdapter(conn=Mock())
infoblox_adapter.job = Mock()
nautobot_adapter.sync_to(infoblox_adapter)

prefix = Prefix.objects.get(network="10.0.0.0", prefix_length="8")
self.assertEqual(prefix.tags.all()[0], self.tag_sync_to_infoblox)

ipaddress = IPAddress.objects.get(address="10.0.0.1/8")
self.assertEqual(ipaddress.tags.all()[0], self.tag_sync_to_infoblox)

vlan = VLAN.objects.get(vid=750)
self.assertEqual(vlan.tags.all()[0], self.tag_sync_to_infoblox)


class TestCustomFields(TestCase):
"""Tests ensuring custom fields are updated for objects synced from and to Infoblox."""

def setUp(self):
"""Test class set up."""
self.today = datetime.date.today().isoformat()
self.cf_synced_to_infoblox = CustomField.objects.get(key="ssot_synced_to_infoblox")

def test_cfs_have_correct_content_types_set(self):
"""Ensure cfs have correct content types configured."""
for model in (IPAddress, Prefix, VLAN, VLANGroup):
content_type = ContentType.objects.get_for_model(model)
self.assertIn(content_type, self.cf_synced_to_infoblox.content_types.all())

def test_cf_updated_for_objects_synced_to_infoblox(self):
"""Ensure objects synced to Infoblox have cf 'ssot_synced_to_infoblox' correctly updated."""
nb_prefix = Prefix(
network="10.0.0.0",
prefix_length=8,
description="Test Network",
type="network",
status=Status.objects.get_for_model(Prefix).first(),
)
nb_prefix.validated_save()

nb_ipaddress = IPAddress(
description="Test IPAddress",
address="10.0.0.1/8",
status=Status.objects.get_for_model(IPAddress).first(),
type="host",
)
nb_ipaddress.validated_save()

nb_vlangroup = VLANGroup(
name="TestVLANGroup",
)
nb_vlangroup.validated_save()
nb_vlan = VLAN(
vid=750,
name="VL750",
description="Test VLAN",
status=Status.objects.get_for_model(VLAN).first(),
vlan_group=nb_vlangroup,
)
nb_vlan.validated_save()

nautobot_adapter = NautobotAdapter()
nautobot_adapter.job = Mock()
nautobot_adapter.load()

conn = Mock()
infoblox_adapter = InfobloxAdapter(conn=conn)
infoblox_adapter.job = Mock()
nautobot_adapter.sync_to(infoblox_adapter)

prefix = Prefix.objects.get(network="10.0.0.0", prefix_length="8")
self.assertEqual(prefix.cf["ssot_synced_to_infoblox"], self.today)

ipaddress = IPAddress.objects.get(address="10.0.0.1/8")
self.assertEqual(ipaddress.cf["ssot_synced_to_infoblox"], self.today)

vlangroup = VLANGroup.objects.get(name="TestVLANGroup")
self.assertEqual(vlangroup.cf["ssot_synced_to_infoblox"], self.today)

vlan = VLAN.objects.get(vid=750)
self.assertEqual(vlan.cf["ssot_synced_to_infoblox"], self.today)

0 comments on commit 6e4c42b

Please sign in to comment.