Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LocationType Specification to DNA Center and Device42 #546

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
409a43b
refactor: 🚚 Consolidate all custom Exceptions for integrations into o…
jdrew82 Sep 12, 2024
e8c58ce
feat: ✨ Update Job form to add fields to specify LocationType for Are…
jdrew82 Sep 12, 2024
6634f90
feat: ✨ Add location_type attribute to Area, Building, and Floor Diff…
jdrew82 Sep 12, 2024
9fc6cbf
refactor: 🔥 Remove Region and Site LocationTypes being created in sig…
jdrew82 Sep 12, 2024
d8184aa
test: ✅ Update tests to factor in updates to DiffSync models and adap…
jdrew82 Sep 12, 2024
523fab4
fix: 🐛 Update load_controller_locations to use Job form vars and add …
jdrew82 Sep 12, 2024
c0d8cf4
refactor: Set has_sensitive_variables to False
jdrew82 Sep 12, 2024
8e457e9
docs: 📝 Add documentation on using DNA Center integration along with …
jdrew82 Sep 12, 2024
dfe4dcb
docs: 📝 Add changelog fragments
jdrew82 Sep 12, 2024
89a9980
refactor: 🔥 Remove signals that creates Site LocationType and adds Co…
jdrew82 Sep 15, 2024
22d85f3
feat: ✨ Add Building LocationType Job form var to allow user to defin…
jdrew82 Sep 15, 2024
a8f079b
feat: Add IPAM DiffSync models to dunder all
jdrew82 Sep 15, 2024
630b0ce
feat: ✨ Add location_type and building_loctype attributes to Building…
jdrew82 Sep 15, 2024
c3e8850
test: ✅ Update tests to align with changes to DiffSync models.
jdrew82 Sep 15, 2024
3608913
docs: 📝 Update documentation for Device42 integration to address Job …
jdrew82 Sep 15, 2024
d40175a
docs: 📝 Update changelog snippets to include Device42 changes.
jdrew82 Sep 15, 2024
d93a001
fix: 🐛 Correct DataMapping for Device42 Buildings to go to Locations.
jdrew82 Sep 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changes/546.added
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added support for specifying LocationType for Areas, Buildings, and Floors in DNA Center integration.
Added support for specifying LocationType for Buildings in Device42 integration.
2 changes: 2 additions & 0 deletions changes/546.documentation
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added documentation on how to use DNA Center integration along with screenshots of the steps.
Updated documentation for Device42 integration and updated Job form screenshot to update for Building LocationType Job form change.
Binary file modified docs/images/device42_job-form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dnac_controller.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dnac_dashboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dnac_detail-view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dnac_enabled_job.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dnac_external_integration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dnac_job_form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dnac_job_list.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dnac_job_settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dnac_jobresult.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dnac_password_secret.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dnac_secretsgroup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dnac_ssot-sync-details.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/dnac_username_secret.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions docs/user/integrations/device42.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ From Device42 into Nautobot, it synchronizes the following objects:

| Device42 | Nautobot |
| ----------------------- | ---------------------------- |
| Buildings | Sites |
| Buildings | Locations |
| Rooms | RackGroups |
| Racks | Racks |
| Vendors | Manufacturers |
Expand Down Expand Up @@ -36,7 +36,9 @@ To start the synchronization, simply select the ExternalIntegration that corresp

![Job Form](../../images/device42_job-form.png)

If you wish to just test the synchronization but not have any data created in Nautobot you'll want to select the `Dry run` toggle. Clicking the `Debug` toggle will enable more verbose logging to inform you of what is occuring behind the scenes. Finally, the `Bulk import` option will enable bulk create and update operations to be used when the synchronization is complete. This can improve performance times for the App by forsaking validation of the imported data. Be aware that this could potentially cause bad data to be pushed into Nautobot.
> As of SSoT 3.2.0 you now have the option to define the LocationType to use for imported Buildings. If unspecifed in the Job form it will resort to using the Site LocationType as it did previously.

If you wish to just test the synchronization but not have any data created in Nautobot you'll want to select the `Dryrun` toggle. Clicking the `Debug` toggle will enable more verbose logging to inform you of what is occuring behind the scenes. Finally, the `Bulk import` option will enable bulk create and update operations to be used when the synchronization is complete. This can improve performance times for the App by forsaking validation of the imported data. Be aware that this could potentially cause bad data to be pushed into Nautobot.

Running this Job will redirect you to a `Nautobot Job Result` view.

Expand Down
81 changes: 81 additions & 0 deletions docs/user/integrations/dna_center.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Cisco DNA Center SSoT Integration

The Cisco DNA Center SSoT integration is built as part of the [Nautobot Single Source of Truth (SSoT)](https://github.com/nautobot/nautobot-app-ssot) app. The SSoT app enables Nautobot to be the aggregation point for data coming from multiple systems of record (SoR).

From Cisco DNA Center into Nautobot, it synchronizes the following objects:

| DNA Center | Nautobot |
| ----------------------- | ---------------------------- |
| Areas | Location* |
| Buildings | Location* |
| Floors | Location* |
| Devices | Devices |
| Ports | Interfaces |
| Prefixes | Prefixes |
| IP Addresses | IP Addresses |

`*` As of SSoT 3.2.0 the LocationType for Areas, Buildings, and Floors can be defined in the Job form. Prior to SSoT 3.2.0 the DNA Center integration creates a Region, Site, and Floor LocationType and imports Areas as Region Locations, Buildings as Site Locations, and Floors as Floor Locations.

`**` If the [Device Lifecycle Nautobot app](https://github.com/nautobot/nautobot-app-device-lifecycle-mgmt) is found to be installed, a matching Version will be created with a RelationshipAssociation connecting the Device and that Version.

## Usage

Once the app is installed and configured, you will be able to perform an inventory ingestion from DNA Center into Nautobot. From the Nautobot SSoT Dashboard view (`/plugins/ssot/`), DNA Center will show as a Data Source.

![Dashboard View](../../images/dnac_dashboard.png)

From the Dashboard, you can also view more information about the App by clicking on the `DNA Center to Nautobot` link and see the Detail view. This view will show the mappings of DNA Center objects to Nautobot objects, the sync history, and other configuration details for the App:

![Detail View](../../images/dnac_detail-view.png)

In order to utilize this integration you must first enable the Job. You can find the available installed Jobs under Jobs -> Jobs:

![Job List](../../images/dnac_job_list.png)

To enable the Job you must click on the orange pencil icon to the right of the `DNA Center to Nautobot` Job. You will be presented with the settings for the Job as shown below:

![Job Settings](../../images/dnac_job_settings.png)

You'll need to check the `Enabled` checkbox and then the `Update` button at the bottom of the page. You will then see that the play button next to the Job changes to blue and becomes functional, linking to the Job form.

![Enabled Job](../../images/dnac_enabled_job.png)

Once the Job is enabled, you'll need to manually create a few objects in Nautobot to use with the Job. First, you'll need to create a Secret that contains the username and password for authenticating to your desired DNA Center instance:

![Username Secret](../../images/dnac_username_secret.png)

![Password Secret](../../images/dnac_password_secret.png)

Once the required Secrets are created, you'll need to create a SecretsGroup that pairs them together and defines the Access Type of HTTP(S) like shown below:

![DNAC SecretsGroup](../../images/dnac_secretsgroup.png)

With the SecretsGroup defined containing your instance credentials you'll then need to create an ExternalIntegration object to store the information about the DNA Center instance you wish to synchronize with.

![DNAC ExternalIntegration](../../images/dnac_external_integration.png)

> The only required portions are the Name, Remote URL, Verify SSL, and Secrets Group. The `Extra Config` section allows you to specify the port that DNA Center is running on. It will default to 443 if unspecified.

The final step before running the Job is to create a Controller that references the ExternalIntegration that you just created. You can attach a `Managed Device Group` to the Controller for all imported Devices to be placed in. If you don't create one, one will be created automatically and associated to the specified Controller with the name of `<Controller name> Managed Devices`.

![DNAC Controller](../../images/dnac_controller.png)

> You can utilize multiple DNA Center Controllers with this integration as long as you specify a unique Tenant per Controller. The failure to use differing Tenants will have the Devices, Prefixes, and IPAddresses potentially removed if they are non-existent on the additional Controller. Locations should remain unaffected.

With those configured, you will then need to define a LocationType to use for each DNA Center location type of Areas, Buildings, and Floors. With those created, you can run the Job to start the synchronization:

![Job Form](../../images/dnac_job_form.png)

If you wish to just test the synchronization but not have any data created in Nautobot you'll want to select the `Dryrun` toggle. Clicking the `Debug` toggle will enable more verbose logging to inform you of what is occuring behind the scenes. Finally, the `Bulk import` option will enable bulk create and update operations to be used when the synchronization is complete. This can improve performance times for the integration by forsaking validation of the imported data. Be aware that this could potentially cause bad data to be pushed into Nautobot. After those toggles there are also dropdowns that allow you to specify the DNA Center Controller to synchronize with and to define the LocationTypes to use for the imported Areas, Buildings, and Floors from DNA Center. In addition, there are also some optional settings on the Job form:

- The Location Mapping allows you to define a dictionary of Location mappings. This feature is intended for specifying parent Locations for the Areas and Building locations in DNA Center. This is useful if this information is missing from DNA Center but required for Nautobot or to allow you to change the information as it's imported to match information from another System of Record. The expected pattern for this field is `{"<Location Name>": {"parent": "<Parent location Name>"}}`.

- Finally there is an option to specify a Tenant to be assigned to the imported Devices, Prefixes, and IPAddreses. This is handy for cases where you have multiple DNA Center instances that are used by differing business units.

Running this Job will redirect you to a `Nautobot Job Result` view.

![JobResult View](../../images/dnac_jobresult.png)

Once the Job has finished you can access the `SSoT Sync Details` page to see detailed information about the data that was synchronized from DNA Center and the outcome of the sync Job.

![SSoT Sync Details](../../images/dnac_ssot-sync-details.png)
1 change: 1 addition & 0 deletions docs/user/integrations/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This Nautobot app supports the following integrations:
- [Cisco ACI](./aci.md)
- [Arista CloudVision](./aristacv.md)
- [Device42](./device42.md)
- [Cisco DNA Center](./dna_center.md)
- [Infoblox](./infoblox.md)
- [IPFabric](./ipfabric.md)
- [Itential](./itential.md)
Expand Down
67 changes: 67 additions & 0 deletions nautobot_ssot/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Custom Exceptions to be used with SSoT integrations."""


class AdapterLoadException(Exception):
"""Raised when there's an error while loading data."""


class AuthFailure(Exception):
"""Exception raised when authenticating to on-prem CVP fails."""

def __init__(self, error_code, message):
"""Populate exception information."""
self.expression = error_code
self.message = message
super().__init__(self.message)


class ConfigurationError(Exception):
"""Exception thrown when Job configuration is wrong."""


class JobException(Exception):
"""Exception raised when failure loading integration Job."""

def __init__(self, message):
"""Populate exception information."""
self.message = message
super().__init__(self.message)


class InvalidUrlScheme(Exception):
"""Exception raised for wrong scheme being passed for URL.

Attributes:
message (str): Returned explanation of Error.
"""

def __init__(self, scheme):
"""Initialize Exception with wrong scheme in message."""
self.message = f"Invalid URL scheme '{scheme}' found for Infoblox URL. Please correct to use HTTPS."
super().__init__(self.message)


class MissingConfigSetting(Exception):
"""Exception raised for missing configuration settings.

Attributes:
message (str): Returned explanation of Error.
"""

def __init__(self, setting):
"""Initialize Exception with Setting that is missing and message."""
self.setting = setting
self.message = f"Missing configuration setting - {setting}!"
super().__init__(self.message)


class MissingSecretsGroupException(Exception):
"""Custom Exception in case SecretsGroup is not found on ExternalIntegration."""


class RequestConnectError(Exception):
"""Exception class to be raised upon requests module connection errors."""


class RequestHTTPError(Exception):
"""Exception class to be raised upon requests module HTTP errors."""
10 changes: 2 additions & 8 deletions nautobot_ssot/integrations/aci/diffsync/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import requests
import urllib3

from nautobot_ssot.exceptions import RequestConnectError, RequestHTTPError

from .utils import (
ap_from_dn,
bd_from_dn,
Expand All @@ -26,14 +28,6 @@
logger = logging.getLogger(__name__)


class RequestConnectError(Exception):
"""Exception class to be raised upon requests module connection errors."""


class RequestHTTPError(Exception):
"""Exception class to be raised upon requests module HTTP errors."""


class AciApi:
"""Representation and methods for interacting with aci."""

Expand Down
5 changes: 1 addition & 4 deletions nautobot_ssot/integrations/aci/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from nautobot.dcim.models import Controller, Location
from nautobot.extras.jobs import BooleanVar, Job, ObjectVar

from nautobot_ssot.exceptions import ConfigurationError
from nautobot_ssot.integrations.aci.diffsync.adapters.aci import AciAdapter
from nautobot_ssot.integrations.aci.diffsync.adapters.nautobot import NautobotAdapter
from nautobot_ssot.integrations.aci.diffsync.client import AciApi
Expand All @@ -14,10 +15,6 @@
name = "Cisco ACI SSoT" # pylint: disable=invalid-name, abstract-method


class ConfigurationError(Exception):
"""Exception thrown when Job configuration is wrong."""


class AciDataSource(DataSource, Job): # pylint: disable=abstract-method, too-many-instance-attributes
"""ACI SSoT Data Source."""

Expand Down
15 changes: 1 addition & 14 deletions nautobot_ssot/integrations/aristacv/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from nautobot.dcim.models import DeviceType
from nautobot.extras.jobs import BooleanVar, Job

from nautobot_ssot.exceptions import MissingConfigSetting
from nautobot_ssot.integrations.aristacv.diffsync.adapters.cloudvision import CloudvisionAdapter
from nautobot_ssot.integrations.aristacv.diffsync.adapters.nautobot import NautobotAdapter
from nautobot_ssot.integrations.aristacv.utils.cloudvision import CloudvisionApi
Expand All @@ -16,20 +17,6 @@
name = "SSoT - Arista CloudVision" # pylint: disable=invalid-name


class MissingConfigSetting(Exception):
"""Exception raised for missing configuration settings.

Attributes:
message (str): Returned explanation of Error.
"""

def __init__(self, setting):
"""Initialize Exception with Setting that is missing and message."""
self.setting = setting
self.message = f"Missing configuration setting - {setting}!"
super().__init__(self.message)


class CloudVisionDataSource(DataSource, Job): # pylint: disable=abstract-method
"""CloudVision SSoT Data Source."""

Expand Down
11 changes: 1 addition & 10 deletions nautobot_ssot/integrations/aristacv/utils/cloudvision.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from cvprac.cvp_client import CvpClient, CvpLoginError
from google.protobuf.wrappers_pb2 import StringValue # pylint: disable=no-name-in-module

from nautobot_ssot.exceptions import AuthFailure
from nautobot_ssot.integrations.aristacv.constants import PORT_TYPE_MAP
from nautobot_ssot.integrations.aristacv.types import CloudVisionAppConfig

Expand All @@ -31,16 +32,6 @@
UPDATES_TYPE = List[UPDATE_TYPE]


class AuthFailure(Exception):
"""Exception raised when authenticating to on-prem CVP fails."""

def __init__(self, error_code, message):
"""Populate exception information."""
self.expression = error_code
self.message = message
super().__init__(self.message)


class CloudvisionApi: # pylint: disable=too-many-instance-attributes, too-many-arguments
"""Arista CloudVision gRPC client."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ def load_buildings(self):
_tags.sort()
building = self.building(
name=record["name"],
location_type=self.job.building_loctype.name,
address=sanitize_string(record["address"]) if record.get("address") else "",
latitude=float(round(Decimal(record["latitude"] if record["latitude"] else 0.0), 6)),
longitude=float(round(Decimal(record["longitude"] if record["longitude"] else 0.0), 6)),
Expand Down Expand Up @@ -235,14 +236,17 @@ def load_rooms(self):
room = self.room(
name=record["name"],
building=record["building"],
building_loctype=self.job.building_loctype.name,
notes=record["notes"] if record.get("notes") else "",
custom_fields=get_custom_field_dict(record["custom_fields"]),
tags=_tags,
uuid=None,
)
try:
self.add(room)
_site = self.get(self.building, record.get("building"))
_site = self.get(
self.building, {"name": record.get("building"), "location_type": self.job.building_loctype.name}
)
_site.add_child(child=room)
except ObjectAlreadyExists as err:
if self.job.debug:
Expand Down Expand Up @@ -275,7 +279,13 @@ def load_racks(self):
try:
self.add(rack)
_room = self.get(
self.room, {"name": record["room"], "building": record["building"], "room": record["room"]}
self.room,
{
"name": record["room"],
"building": record["building"],
"building_loctype": self.job.building_loctype.name,
"room": record["room"],
},
)
_room.add_child(child=rack)
except ObjectAlreadyExists as err:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
FrontPort,
Interface,
Location,
LocationType,
Manufacturer,
Platform,
Rack,
Expand Down Expand Up @@ -161,11 +160,12 @@ def sync_complete(self, source: Adapter, *args, **kwargs):

def load_sites(self):
"""Add Nautobot Site objects as DiffSync Building models."""
for site in Location.objects.filter(location_type=LocationType.objects.get_or_create(name="Site")[0]):
for site in Location.objects.filter(location_type=self.job.building_loctype.name):
self.site_map[site.name] = site.id
try:
building = self.building(
name=site.name,
location_type=self.job.building_loctype.name,
address=site.physical_address,
latitude=site.latitude,
longitude=site.longitude,
Expand All @@ -191,12 +191,15 @@ def load_rackgroups(self):
room = self.room(
name=_rg.name,
building=_rg.location.name,
building_loctype=self.job.building_loctype.name,
notes=_rg.description,
custom_fields=nautobot.get_custom_field_dict(_rg.get_custom_fields()),
uuid=_rg.id,
)
self.add(room)
_site = self.get(self.building, _rg.location.name)
_site = self.get(
self.building, {"name": _rg.location.name, "location_type": self.job.building_loctype.name}
)
_site.add_child(child=room)

def load_racks(self):
Expand Down
10 changes: 10 additions & 0 deletions nautobot_ssot/integrations/device42/diffsync/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
NautobotRoom,
NautobotVendor,
)
from nautobot_ssot.integrations.device42.diffsync.models.nautobot.ipam import (
NautobotIPAddress,
NautobotSubnet,
NautobotVLAN,
NautobotVRFGroup,
)

__all__ = (
"PatchPanel",
Expand Down Expand Up @@ -65,8 +71,12 @@
"NautobotConnection",
"NautobotDevice",
"NautobotHardware",
"NautobotIPAddress",
"NautobotPort",
"NautobotRack",
"NautobotRoom",
"NautobotSubnet",
"NautobotVendor",
"NautobotVLAN",
"NautobotVRFGroup",
)
Loading