Introducing Design Builder: Design Driven Network Automation

Blog Detail

Most people involved in network automation are familiar with the concept of a Source of Truth (SoT). The SoT is usually some form of database that maintains intended state of objects as well as their interdependency. The SoT provides a way to quickly ascertain what a network’s intended state should be, while often providing a way to see what the network’s state actually is. A new concept is emerging, known as Design Oriented Source of Truth. This idea takes network designs and codifies them, attaching additional meaning to the objects within the SoT. Nautobot is a source of truth that contains all sorts of information about a network’s state. Although many of the pieces of information within Nautobot are related, they are discretely managed. A new Nautobot App aims to simplify the process of codifying network designs and populating Nautobot objects based on these designs.

Introduction

It is very common to have a small set of standardized designs that are used to deploy many sites and services in enterprise networks. For example, branch office sites may have a few different designs depending on their size. There could be a design that uses a single branch office router for small sites. Another design could have two routers and an access switch for sites with a moderate user base. A third design could include a more complex switching infrastructure for sites with many employees. When companies do tech refreshes or new site builds, these standardized designs are used and new data must be created in the source of truth. The newly open-sourced Design Builder application was created to address this problem, and fulfills the idea that a standardized design can be taken from a network engineer and transformed into a format that can be consumed and executed by Nautobot. Design Builder can expand a minimal set of inputs into a full-fledged set of configuration objects within Nautobot. This includes any kind of data object that Nautobot can model. Everything from Rack and Device objects to IP addresses and BGP peering information.

Design Builder provides powerful mechanisms that make simple designs possible. The first is the ability to represent interrelated data in a meaningful hierarchy. For example, devices have interfaces and interfaces have IP addresses. Conceptually this seems like a very simple structure. However, if we were to manually use the REST API or ORM to handle creating objects like this, we would first have to create a device object and keep its ID in memory. We would then have to create interfaces with their device foreign-key set to the device ID we just created. Finally, we’d have to save all of the interface IDs and do the same with IP addresses. Design Builder provides a means to represent objects in YAML and produce their representation within the Nautobot database. A typical design workflow follows the following diagram:

Following this process, we can produce YAML files that intuitively represent the structure of the data we want to create. An example of a Design Builder YAML design can be seen in the following YAML document:

devices:
  - name: "Router 1"
    status__name: "Active"
    interfaces:
      - name: "GigabitEthernet0"
        type: "1000base-t"
        status__name: "Active"
        ip_addresses:
          - address: "192.168.0.1/24"
            status__name: "Active"

This YAML document would produce a single device, with a single Gigabit Ethernet interface. The interface itself has a single IP address. As demonstrated in the example, Design Builder automatically associates the parent/child relationships correctly, and there is no need to keep copies of primary and foreign keys. We can visually represent this YAML design with the following diagram:

Design Builder also provides a system to query for existing related objects using some attribute of the associated object. In the above example, the status field is actually a related object. Statuses are not just simple strings, they are first-class objects within the Nautobot database. In this case, the Status object with the name Active is predefined in Nautobot and does not need to be created. It does, however, need to be associated with the Device, the Interface, and the IPAddress objects.

This object relationship is actually a foreign-key relationship in the database and ORM. If we were using the Django ORM to associate objects, we would first need to look up the status before creating the associated objects. Design Builder provides a way to perform that lookup as part of the model hierarchy. Note that we’re looking up the status by its name: status__name. Design Builder has adopted similar syntax to Django’s field lookup. The field name and related field are separated by double underscores.

Use Cases

There are many use cases that are covered by the Design Builder, but we will highlight a very simple one in this post. Our example use case handles the creation of edge site designs within Nautobot. This use case is often seen when doing tech refreshes or new site build-outs.

Engineers commonly need to add a completely new set of data for a site. This could be the result of a project to refresh a site’s network infrastructure or it could be part of deploying a new site entirely. Even with small sites, the number of objects needing to be created or updated in Nautobot could be dozens or even hundreds. However, if a standardized design is developed then Design Builder can be used to auto-populate all of the data for new or refreshed sites.

Consider the following design, which will create a new site with edge routers, a single /24 prefix and two circuits for the site:

---
sites:
  - name: "LWM1"
    status__name: "Staging"
    prefixes:
      - prefix: "10.37.27.0/24"
        status__name: "Reserved"
    devices:
      - name: "LWM1-LR1"
        status__name: "Planned"
        device_type__model: "C8300-1N1S-6T"
        device_role__name: "Edge Router"
        interfaces:
          - name: "GigabitEthernet0/0"
            type: "1000base-t"
            description: "Uplink to backbone"
            status__name: "Planned"
      - name: "LWM1-LR2"
        status__name: "Planned"
        device_type__model: "C8300-1N1S-6T"
        device_role__name: "Edge Router"      
        interfaces:
          - name: "GigabitEthernet0/0"
            type: "1000base-t"
            description: "Uplink to backbone"
            status__name: "Planned"

circuits:
  - cid: "LWM1-CKT-1"
    status__name: "Planned"
    provider__name: "NTC"
    type__name: "Ethernet"
    terminations:
      - term_side: "A"
        site__name: "LWM1"
      - term_side: "Z"
        provider_network__name: "NTC-WAN"

  - cid: "LWM1-CKT-2"
    status__name: "Planned"
    provider__name: "NTC"
    type__name: "Ethernet"
    terminations:
      - term_side: "A"
        site__name: "LWM1"
      - term_side: "Z"
        provider_network__name: "NTC-WAN"

This is still quite a bit of information to write. Luckily, the Design Builder application can consume Jinja templates to produce the design files. Using some Jinja templating, we can reduce the above design a bit:


---
sites:
  - name: "LWM1"
    status__name: "Staging"
    prefixes:
      - prefix: "10.37.27.0/24"
        status__name: "Reserved"
    devices:
    {% for i in range(2) %}
      - name: "LWM1-LR{{ i }}"
        status__name: "Planned"
        device_type__model: "C8300-1N1S-6T"
        device_role__name: "Edge Router"
        interfaces:
          - name: "GigabitEthernet0/0"
            type: "1000base-t"
            description: "Uplink to backbone"
            status__name: "Planned"
    {% endfor %}
circuits:
  {% for i in range(2) %}
  - cid: "LWM1-CKT-{{ i }}"
    status__name: "Planned"
    provider__name: "NTC"
    type__name: "Ethernet"
    terminations:
      - term_side: "A"
        site__name: "LWM1"
      - term_side: "Z"
        provider_network__name: "NTC-WAN"
  {% endfor %}

The above design file gets closer to a re-usable design. It has reduced the amount of information we have to represent by leveraging Jinja2 control structures, but there is still statically defined information. At the moment, the design includes hard coded site information (for the site name, device names and circuit IDs) as well as a hard coded IP prefix. Design Builder also provides a way for this information to be gathered dynamically. Fundamentally, all designs are just Nautobot Jobs. Therefore, a design Job can include user-supplied vars that are then copied into the Jinja2 render context. Consider the design job for our edge site design:

class EdgeDesign(DesignJob):
    """A basic design for design builder."""
    site_name = StringVar(label="Site Name", regex=r"\w{3}\d+")
    site_prefix = IPNetworkVar(label="Site Prefix")

#...

This design Job collects a site_name variable as well as a site_prefix variable from the user. Users provide values for these variables through the normal Job launch entrypoint:

Once the job has been launched, the Design Builder will provide these input variables to the Jinja rendering context. The variable names, within the jinja2 template, will match the attribute names used in the Design Job class. With the site_name and site_prefix variables now being defined dynamically, we can produce a final design document using them:

---

sites:
  - name: "{{ site_name }}"
    status__name: "Staging"
    prefixes:
      - prefix: "{{ site_prefix }}"
        status__name: "Reserved"
    devices:
    {% for i in range(2) %}
      - name: "{{ site_name }}-LR{{ i }}"
        status__name: "Planned"
        device_type__model: "C8300-1N1S-6T"
        device_role__name: "Edge Router"
        interfaces:
          - name: "GigabitEthernet0/0"
            type: "1000base-t"
            description: "Uplink to backbone"
            status__name: "Planned"
    {% endfor %}
circuits:
  {% for i in range(2) %}
  - cid: "{{ site_name }}-CKT-{{ i }}"
    status__name: "Planned"
    provider__name: "NTC"
    type__name: "Ethernet"
    terminations:
      - term_side: "A"
        site__name: "{{ site_name }}"
      - term_side: "Z"
        provider_network__name: "NTC-WAN"
  {% endfor %}

The design render context is actually much more flexible than simple user entry via script vars. Design Builder provides a complete system for managing the render context, including loading variables from YAML files and providing dynamic content via Python code. The official documentation covers all of the capabilities of the design context.

In addition to the YAML rendering capabilities, Design Builder includes a way to perform just-in-time operations while creating and updating Nautobot objects. For instance, in the above example, the site prefix is specified by the user that launches the job. It may be desirable for this prefix to be auto-assigned and provisioned out of a larger parent prefix. Design Builder provides a means to perform these just-in-time lookups and calculations in the form of something called an “action tag”. Action tags are evaluated during the object creation phase of a design’s implementation. That means that database lookups can occur and computations can take place as the design is being implemented. One of the provided action tags is the next_prefix action tag. This tag accepts query parameters to find a parent prefix, and also a parameter that specifies the length of the required new prefix. For example, if we want to provision a /24 prefix from the 10.0.0.0/16 parent, we could use the following:

prefixes:
  - "!next_prefix":
      prefix: "10.0.0.0/16"
      length: 24
    status__name: "Active"

The next_prefix action tag will find the parent prefix 10.0.00/16 and look for the first available /24 in that parent. Once found, Design Builder will create that child prefix with the status Active.

Several action tags are provided out of the box, but one of the most powerful features of Design Builder is the ability to include custom action tags in a design. Action tags are implemented in Python as specialized classes, and can perform any operation necessary to produce a just-in-time result.

There is quite a lot to understand with Design Builder, and we have only touched on a few of its capabilities. While there are several moving parts, the following diagram illustrates the high-level process that the Design Builder application uses to go from design files and templates to an implemented design.

Design Builder starts with some optional input variables from the Nautobot job and combines them with optional context variables written either in YAML or Python or both. This render context is used by the Jinja2 renderer to resolve variable names in Jinja2 templates. The Jinja2 templates are rendered into YAML documents that are unmarshaled as Python dictionaries and provided to the Builder. The Builder iterates all of the objects in this dictionary and performs necessary database creations and updates. In the process of creating and updating objects, any action tags that are present are evaluated. The final result is a set of objects in Nautobot that have been created or updated by Design Builder.

Roadmap

Our plans for Design Builder are far from over. There are many more features we’re currently working on, as well as some that are still in the planning stages. Some of the near-term features include design lifecycle and object protection.

The design lifecycle feature allows the implementations of a design to be tracked. Design instances can be created (such as an instance of the edge site design above) and can be subsequently decommissioned. Objects that belong to a design instance will be reverted to their state prior to the design implementation, or they may be removed entirely (if created specifically for a design). Designs can also track inter-design dependencies so that a design cannot be decommissioned if other design instances depend on it. The design lifecycle feature will also allow designs to be versioned so that an implementation can be updated over time.

The ability to protect objects that belong to a design is also planned. The idea is that if an object is created as part of a design implementation, any attributes that were initially set in this design cannot be updated outside of that design’s lifecycle. This object protection assures that our source of truth has data that complies with a design and prevents manually introduced errors.


Conclusion

Design Builder is a great tool that ensures your network designs are used for every deployment, and simplifies populating data in Nautobot along the way. It provides a streamlined way to represent hierarchical relationships with a clear syntax and concepts that should be familiar to those that have started to embark on their NetDevOps journey. I encourage you to try it out.

-Andrew, Christian and Paddy



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!

Nautobot ChatOps for IP Fabric

Blog Detail

The Nautobot ChatOps framework provides a way to efficiently communicate and collaborate with a variety of Operational Support Systems (OSS) and general IT tools. It leverages the plugin architecture to build on Nautobot’s Source of Truth (SoT) capabilities by providing messaging and ChatOps functionality. Communication is not limited to SoT data, interaction with other systems is supported too. The latest addition to the Nautobot ChatOps ecosystem introduces an integration with IP Fabric, a vendor-neutral automated network assurance platform.

IP Fabric supports complex multi-vendor network discovery and gives you full visibility of inventory, configuration, topology and behaviour, allowing you to validate and report on the status of the network.

Our new Nautobot ChatOps application utilizes IP Fabric data to rapidly display network state in a number of supported chat platforms. The ChatOps application for IP Fabric will enable users to dynamically query an IP Fabric system and deliver results in a structured and highly visual format.

Architecture

Chat platforms and their applications have become a common part of enterprise tools. The Nautobot ChatOps for IP Fabric application includes integrations for multiple platforms, including Slack, MS Teams, WebEx and Mattermost.

Deployment requires a bot to be enabled in the chosen messaging platform. This bot will send user requests to your Nautobot server, which will communicate with an IP Fabric system using its API (Application Programming Interface).

Users will have access to dedicated commands to query and visualize their network from their chat application.

Commands

IP Fabric automates the retrieval of network infrastructure data and allows engineers to analyze their networks in predefined snapshots. The new ChatOps integration leverages the IP Fabric API to offer a subset of the systems capabilities to support network operations teams in network troubleshooting and validation in any environment you can install an instant messaging client.

For installation instructions, please refer to the installation section in the plugin repository.

Use the /ipfabric top-level chat command in your chosen chat platform to see the supported subcommands:

  • /ipfabric set-snapshot [snapshot]
  • /ipfabric get-snapshot
  • /ipfabric get-inventory [filter-key] [filter-value]
  • /ipfabric interfaces [device] [metric]
  • /ipfabric end-to-end-path [src-ip] [dst-ip] [src-port] [dst-port] [protocol]
  • /ipfabric pathlookup [src-ip] [dst-ip] [src-port] [dst-port] [protocol]
  • /ipfabric routing [device] [protocol] [filter-opt]
  • /ipfabric wireless [option] [ssid]
  • /ipfabric find-host [filter-key] [filter-value]

Snapshot

IP Fabric can take a snapshot of a network to compare real-time and historical trends. A snapshot captures network state and configuration at a specific point in time. The ChatOps plugin supports querying a snapshot by setting a base snapshot with /ipfabric set-snapshot command. The snapshot is set per user and cached for all future commands. If a snapshot is not set, the commands will default to $last unless a specific snapshot id is required.

Enter the /ipfabric set-snapshot command to prompt a menu to select a snapshot from the available list of snapshots on the IP Fabric server.

/ipfabric set-snapshot

Once a snapshot is selected from the list, the snapshot will be cached in the user’s session for future interactions.

If you already know the snapshot ID, simply enter that as the [snapshot] argument to the command, to avoid using the menu.

/ipfabric set-snapshot e6a53bd9-b38b-4423-b0e3-f01b8bac08ac

At any time, you can use the /ipfabric get-snapshot to display the current snapshot in the user’s session.

/ipfabric get-snapshot

Inventory

IP Fabric maintains an extensive network inventory after initial discovery. Use the /ipfabric get-inventory to display a menu allowing the user to filter inventory data based on:

  • Site
  • Model
  • Vendor
  • Platform
/ipfabric get-inventory

As an example, selecting a Vendor filter will display another submenu with all of the known vendors in the IP Fabric inventory.

To display all Cisco devices, we selected cisco and the results are returned in a well-formatted table with the selection filters chosen and a convenient link to the IP Fabric inventory table for further investigation in a web browser, if necessary.

Interface

The /ipfabric interface command allows a user to quickly troubleshoot interface-related issues on a particular device by reporting the inventory table for the device, based on interface metrics listed below:

  • Load
  • Errors
  • Drops
/ipfabric interface

Select the device and metric to display the interface table with specific columns depending on the chosen metric.

Path Trace

A common task for any network engineer is to trace a path from a source and destination IP address. End-to-end path simulation in IP Fabric provides a powerful analysis of per hop routing decisions and what devices/sites are traversed.

Using the /ipfabric end-to-end-path command, we can display some of the main routing information required to understand the path taken between a source and destination address. This command is compatible with IP Fabric OS software version 3.

Enter the command to display a dialog box prompting to enter the arguments to produce the simulation.

  • Source IP
  • Destination IP
  • Source Port
  • Destination Port
  • Procotol
/ipfabric end-to-end-path

Enter the arguments in the dialog box and hit Submit.

Power users might prefer to enter the arguments directly into the command as follows, which will produce the same output.

/ipfabric end-to-end-path 10.0.20.7 10.0.10.5 1000 22 tcp

Path Diagram

IP Fabric OS version 4 release added API support for retrieving a diagram of a path simulation.

The /ipfabric pathlookup command will render a diagram depicting the path in your messaging application. This command is compatible with IP Fabric OS software version 4.

/ipfabric pathlookup

Input parameters are the same as the end-to-end-path command.

  • Source IP
  • Destination IP
  • Source Port
  • Destination Port
  • Procotol

If the path can be generated by IP Fabric, the diagram will be displayed directly within the chat application. Users can then download or forward the diagram based on the chosen chat platform capabilities.

Routing

Routing support in the plugin focuses on the BGP peering inventory in IP Fabric. Additional routing protocols can be easily added in line with the routing technology tables incorporated with IP Fabric.

/ipfabric routing

Entering the /ipfabric routing command will allow the user to filter for BGP peerings on a device based on a specific BGP state.

The resulting BGP table shows the local BGP speaker and BGP peer information.

Wireless

Wireless LANs (Local Area Network) are a staple part of most enterprise networks. Engineers supporting these networks will need to have information on Service Set Identifiers (SSIDs) and the clients connected to an SSID.

The /ipfabric wireless command will prompt the user to select what type of wireless information they’d like to view.

  • SSIDs
  • Clients
/ipfabric wireless

Selecting the ssids option displays a table with all of the known SSID’s in IP Fabric, their access points, and the number of connected clients.

Selecting the clients option prompts another filter to select a specific SSID. The resulting table provides greater detail on the client, their connected Wireless LAN Controller (WLC), assigned IPs, signal strength, and the client’s state.

Find Host

Using the host inventory table, a user can quickly find a host and what devices they’re connected to with the /ipfabric find-host command. Select how to filter the host search using the options below.

  • Host IP Address
  • Host MAC Address
/ipfabric find-host

The resulting table shows the host inventory information.


Conclusion

IP Fabric stores a huge amount of data that is vital for supporting large-scale networks. With the Nautobot IP Fabric ChatOps application, we’ve tried to focus on useful operations that most network engineers will perform regularly. If you have any suggestions for other commands, please visit the plugin repository, create a feature request or discussion, or even open a Pull Request! We’d love to hear about it. You can also visit the ipfabric channel on the Network to Code Slack.

-Paddy

Resources



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!

Nautobot Relationships Part 2

Blog Detail

In Part 1 we introduced Nautobot’s new custom defined Relationships feature. Potential use cases were identified, and the process to configure the relationships using the web user interface was outlined. Nautobot Relationships are supported by the REST API (Application Programming Interface) for standard CRUD (Create, Read, Update, Delete) operations and also by the GraphQL API for read-only operations. This post will outline how to leverage the API, for a use case described in Part 1, to programmatically interact with custom-defined relationships.

REST

For most systems, REST is the de facto API exposed to clients. It is an HTTP-based service that supports stateless client-server interaction for data retrieval of each model in the Nautobot database. For a more detailed overview, check out the Nautobot docs on REST.

HTTP has functions that align to the typical CRUD operations. Nautobot’s RESTful API uses these HTTP functions to interact with the objects in the database. The same operations supported on the GUI are supported on the API. To demonstrate, we’ll create the ‘Circuit IPAddress’ relationship using the API.

Create Relationship: IPAddress – Circuit

The first step when dealing with the API is to consult the endpoint documentation. On the Home page, select the API button in the bottom right corner.

Nautobot API Documentation

The API button will redirect to <server_url>/api/docs/ which is the Nautobot API endpoint documentation powered by Swagger. To create a Relationship object a POST operation to the /extras/relationships/ endpoint is required. The docs outline that the data field for the POST method has a number of required fields, highlighted with a red asterisk.

POST /extras/relationships/

To execute the HTTP POST, we’ll develop a HTTP client using the Python requests HTTP library, which supports REST and non-REST API communications. Alternatively, the Swagger documentation can be used to test REST API requests directly in the web browser.

When creating the Relationship, a valid slug field consisting of letters, numbers, underscores or hyphens is required to uniquely identify this new object. In the GUI, this field is automatically generated; but in the API it needs to be manually defined. All other fields should be as described in the previous post.

INFO: Nautobot release 1.2 will introduce auto-generated slug fields for the API.

server_url = "https://demo.nautobot.com"
url = f"{server_url}/api/extras/relationships/"

token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

payload = {
    "name": "Circuit IPAddress",
    "description": "Custom relationship between circuits and IP addresses",
    "slug": "circuit-ipaddress",
    "type": "one-to-many",
    "source_type": "circuits.circuit",
    "source_label": "IP Address",
    "destination_type": "ipam.ipaddress",
    "destination_label": "Circuit",
}
headers = {"Authorization": f"Token {token}"}

response = requests.request("POST", url, headers=headers, json=payload)
print(json.dumps(response.json(), indent=2))

If the object was created successfully, the HTTP return code of 201 is returned, along with the JSON definition of the object.

{
  "id": "2b13240d-cec0-4eb5-a640-ce8f9f47fd9c",
  "url": "http://demo.nautobot.com/api/extras/relationships/2b13240d-cec0-4eb5-a640-ce8f9f47fd9c/",
  "name": "Circuit IPAddress",
  "slug": "circuit-ipaddress",
  "description": "Custom relationship between circuits and IP addresses",
  "type": "one-to-many",
  "source_type": "circuits.circuit",
  "source_label": "IP Address",
  "source_hidden": false,
  "source_filter": null,
  "destination_type": "ipam.ipaddress",
  "destination_label": "Circuit",
  "destination_hidden": false,
  "destination_filter": null
}

Relationship Association

The Relationship Association is a way to use the new model Relationship. It links instances of the models used in the source_type and destination_type. It’s analogous to selecting the remote end of the relationship using a drop-down list on the web GUI.

To create the association using the REST API, the uuid of each instance in the association is configured in the source_id and destination_id. The relationship field can also be configured with a uuid or, as in the example, a slug field can be provided within a dictionary, causing Nautobot to do an object lookup.

The uuid values can be retrieved with a GET request to the specific model API endpoint. To simplify the example, we’ll use static object ID’s for each instance in the association. A simple loop will iterate over the two IP address objects to associate each instance with the circuit using the new circuit-ipaddress Relationship.

server_url = "https://demo.nautobot.com"
url = f"{server_url}/api/extras/relationship-associations/"

token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

circuit_id = "8b109292-047d-4140-b711-a9524ebb220c"
ip_addr1_id = "94a902a3-e063-44b6-889c-d3023b901aff"
ip_addr2_id = "8b195702-eca2-4ce3-8d48-68af8d9f5b65"

headers = {"Authorization": f"Token {token}"}

for ip_addr_id in [ip_addr1_id, ip_addr2_id]:
    payload = {
        "relationship": {
            "slug": "circuit-ipaddress",
        },
        "source_type": "circuits.circuit",
        "source_id": circuit_id,
        "destination_type": "ipam.ipaddress",
        "destination_id": ip_addr_id,
    }

    response = requests.request("POST", url, headers=headers, json=payload)

    print(json.dumps(response.json(), indent=2))

Once again, if successfully completed, the HTTP return code of 201 is returned, along with the JSON definition of the object(s).

{
  "id": "830166ef-93af-4eb8-ba56-2fe9e5d15bbe",
  "relationship": {
    "id": "00a17433-3f59-437a-a413-3f6e58e7d5f0",
    "url": "http://demo.nautobot.com/api/extras/relationships/00a17433-3f59-437a-a413-3f6e58e7d5f0/",
    "name": "Circuit IPAddress",
    "slug": "circuit-ipaddress",
    "display": "Circuit IPAddress"
  },
  "source_type": "circuits.circuit",
  "source_id": "8b109292-047d-4140-b711-a9524ebb220c",
  "destination_type": "ipam.ipaddress",
  "destination_id": "94a902a3-e063-44b6-889c-d3023b901aff"
}
{
  "id": "d3d53984-61d5-414a-adaa-eec352baddad",
  "relationship": {
    "id": "00a17433-3f59-437a-a413-3f6e58e7d5f0",
    "url": "http://demo.nautobot.com/api/extras/relationships/00a17433-3f59-437a-a413-3f6e58e7d5f0/",
    "name": "Circuit IPAddress",
    "slug": "circuit-ipaddress",
    "display": "Circuit IPAddress"
  },
  "source_type": "circuits.circuit",
  "source_id": "8b109292-047d-4140-b711-a9524ebb220c",
  "destination_type": "ipam.ipaddress",
  "destination_id": "8b195702-eca2-4ce3-8d48-68af8d9f5b65"
}

Query Relationship Association

In the majority of the cases when using relationships, the client will have an existing object in context, e.g., a circuit. So the query will be from the perspective of that object. The uuid of the circuit can be used to query along with the Relationship slug to get back any relationship information. The circuit was the source_type of the relationship, so the circuit object uuid is provided as a value into the source_id query parameter.

server_url = "http://demo.nautobot.com"
token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

slug = "circuit-ipaddress"
circuit_id = "8b109292-047d-4140-b711-a9524ebb220c"
url = f"{server_url}/api/extras/relationship-associations/?relationship={slug}&source_id={circuit_id}"

headers = {"Authorization": f"Token {token}"}

response = requests.request("GET", url, headers=headers)

print(json.dumps(response.json(), indent=2))

The output return data count shows two objects, representing the two IP address objects associated with the circuit and their JSON data.

{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "830166ef-93af-4eb8-ba56-2fe9e5d15bbe",
      "relationship": {
        "id": "00a17433-3f59-437a-a413-3f6e58e7d5f0",
        "url": "http://demo.nautobot.com/api/extras/relationships/00a17433-3f59-437a-a413-3f6e58e7d5f0/",
        "name": "Circuit IPAddress",
        "slug": "circuit-ipaddress",
        "display": "Circuit IPAddress"
      },
      "source_type": "circuits.circuit",
      "source_id": "8b109292-047d-4140-b711-a9524ebb220c",
      "destination_type": "ipam.ipaddress",
      "destination_id": "94a902a3-e063-44b6-889c-d3023b901aff"
    },
    {
      "id": "d3d53984-61d5-414a-adaa-eec352baddad",
      "relationship": {
        "id": "00a17433-3f59-437a-a413-3f6e58e7d5f0",
        "url": "http://demo.nautobot.com/api/extras/relationships/00a17433-3f59-437a-a413-3f6e58e7d5f0/",
        "name": "Circuit IPAddress",
        "slug": "circuit-ipaddress",
        "display": "Circuit IPAddress"
      },
      "source_type": "circuits.circuit",
      "source_id": "8b109292-047d-4140-b711-a9524ebb220c",
      "destination_type": "ipam.ipaddress",
      "destination_id": "8b195702-eca2-4ce3-8d48-68af8d9f5b65"
    }
  ]
}

GraphQL

GraphQL is a recent technology, designed as a query language for APIs and a server-side application for resolving client queries. Refer to the Resources section, for some excellent material from my colleague Tim Fiola on GraphQL.

In Nautobot, GraphQL supports read-only operations. No updates or modifications can be executed through the GraphQL API. Its power is in complex object data retrieval and the supporting tools for the language, such as GraphiQL, which is an integrated client in Nautobot. GraphiQL can be used to develop queries based on the object model schema to retrieve structured data from the Nautobot GraphQL server. Select the GraphQL button on the bottom-right of the Nautobot Home page.

Nautobot GraphiQL

Query Relationship Association

Nautobot’s GraphQL implementation supports querying custom-defined relationships (and their associations) using the following format, with the hyphens replaced with underscores.

rel_<RELATIONSHIP_SLUG>

Building on the previous example of querying the circuit-ipaddress Relationship.

rel_circuit_ipaddress

In GraphQL a sample circuit query, filtered with a specific cid might look as follows.

GraphiQL Query

Notice how the rel_circuit_ipaddress query object has changed the hyphens to underscores. Also note that attributes available to query within the relationship association depend on the type of object.

The sample query uses the interfacevrf and address fields to show how the Relationship can be used to extract complex association data for the related IP address object. All GraphQL object type fields can be traversed further to retrieve native or custom-defined relationships. This makes it very easy to get all the required data in just one GraphQL query.

An IP address is represented as an IPAddressType in the GraphQL schema. Schema documentation can be viewed using the Docs link on the left-hand side of the GraphQL page. The schema for IPAddressType displays all of the fields available to query.

IPAddressType

Accessing the fields of the model within the relationship using GraphQL is in contrast to the REST API, which returns a reference to the instance. The client then has to make another REST API call to the object endpoint to get the related data.

In addition, the GraphQL server response contains only the fields requested in the query. Notice how this is different from a REST response that returns all fields for an endpoint and the client must filter to extract interested fields.

GraphiQL Response

Conclusion

This series focused on how custom-defined relationships can provide a flexible approach to modeling networks using Nautobot as a source of truth. Use cases facilitated the overall concept; and the various different configuration methods highlight Nautobot’s versatility. Relationships are a key feature in the Nautobot system. If you have any specific use cases please let us know.

-Paddy

Resources



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!