Scalable Network Automation with Python Generators

Blog Detail

In large-scale network automation, memory management can make or break your workflows when dealing with hundreds or thousands of devices. This post dives into how Python generators can help you handle data more efficiently by processing it on-the-fly instead of loading everything into memory. With examples from real-world network automation tasks and tips for identifying and optimizing memory-heavy code, you’ll see how generators can make your automation code more scalable and robust.

The Challenge of Memory Usage in Large-Scale Automation

When prototyping network automation solutions in Python, it’s not uncommon to develop against a small set of devices—only to find that the code doesn’t scale well when used in large production environments. This is often due to the memory overhead of storing large amounts of data in memory, which can lead to performance issues and even crashes. A common approach to fetching data for multiple devices is to use loops or list comprehensions, which can quickly consume memory when dealing with large datasets.

How Python Generators Can Help

Generators in Python are a special type of iterable, similar to a function that returns a list. But instead of returning all the values at once, they yield one value at a time, allowing for lazy evaluation. This means that the values are generated on-the-fly and only when needed, which can be more memory efficient for large environments.

Example of a Typical Network Automation Task

A common use case for network automation is to retrieve data from a remote system, for example a CMDB (Configuration Management Database) or a network SOT (Source of Truth) such as Nautobot. Let’s consider a scenario where we need to fetch device data using Nautobot’s REST API. A traditional approach might involve fetching all the data at once and storing it in a list, like this:

import requests

TEST_API_HEADERS = {
    "Accept": "application/json",
    "Authorization": "Token aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
}


def get_nautobot_devices():
    data = requests.get(
        "https://demo.nautobot.com/api/dcim/devices",
        headers=TEST_API_HEADERS,
        timeout=60,
    ).json()
    devices = data["results"]
    while data["next"]:
        data = requests.get(data["next"], headers=TEST_API_HEADERS, timeout=60).json()
        devices.extend(data["results"])
    return devices

for device in get_nautobot_devices():
    print(device["name"], device["url"])

In this example, Nautobot will return a paginated list of devices, and we’re fetching all of the data for all devices and storing it in a list. Here’s a sample of the data that’s returned for just one device:

{
    "id": "89b2ac3b-1853-4eeb-9ea6-6a081999bd3c",
    "object_type": "dcim.device",
    "display": "ams01-dist-01",
    "url": "https://demo.nautobot.com/api/dcim/devices/89b2ac3b-1853-4eeb-9ea6-6a081999bd3c/",
    "natural_slug": "ams01-dist-01_nautobot-airports_ams01_netherlands_europe_89b2",
    "face": null,
    "local_config_context_data": null,
    "local_config_context_data_owner_object_id": null,
    "name": "ams01-dist-01",
    "serial": "",
    "asset_tag": null,
    "position": null,
    "device_redundancy_group_priority": null,
    "vc_position": null,
    "vc_priority": null,
    "comments": "",
    "local_config_context_schema": null,
    "local_config_context_data_owner_content_type": null,
    "device_type": {
        "id": "4bf23e23-4eb1-4fae-961c-edd6f8cbaaf1",
        "object_type": "dcim.devicetype",
        "url": "https://demo.nautobot.com/api/dcim/device-types/4bf23e23-4eb1-4fae-961c-edd6f8cbaaf1/"
    },
    "status": {
        "id": "9f38bab4-4b47-4e77-b50c-fda62817b2db",
        "object_type": "extras.status",
        "url": "https://demo.nautobot.com/api/extras/statuses/9f38bab4-4b47-4e77-b50c-fda62817b2db/"
    },
    "role": {
        "id": "40567487-6328-4dac-b7b5-b789d1154bf0",
        "object_type": "extras.role",
        "url": "https://demo.nautobot.com/api/extras/roles/40567487-6328-4dac-b7b5-b789d1154bf0/"
    },
    "tenant": {
        "id": "1f7fbd07-111a-4091-81d0-f34db26d961d",
        "object_type": "tenancy.tenant",
        "url": "https://demo.nautobot.com/api/tenancy/tenants/1f7fbd07-111a-4091-81d0-f34db26d961d/"
    },
    "platform": {
        "id": "aa07ca99-b973-4870-9b44-e1ea48c23cc9",
        "object_type": "dcim.platform",
        "url": "https://demo.nautobot.com/api/dcim/platforms/aa07ca99-b973-4870-9b44-e1ea48c23cc9/"
    },
    "location": {
        "id": "9e39051b-e968-4016-b0cf-63a5607375de",
        "object_type": "dcim.location",
        "url": "https://demo.nautobot.com/api/dcim/locations/9e39051b-e968-4016-b0cf-63a5607375de/"
    },
    "rack": null,
    "primary_ip4": null,
    "primary_ip6": null,
    "cluster": null,
    "virtual_chassis": null,
    "device_redundancy_group": null,
    "software_version": null,
    "secrets_group": null,
    "controller_managed_device_group": null,
    "software_image_files": [],
    "created": "2023-09-21T00:00:00Z",
    "last_updated": "2024-09-24T15:20:12.443339Z",
    "notes_url": "https://demo.nautobot.com/api/dcim/devices/89b2ac3b-1853-4eeb-9ea6-6a081999bd3c/notes/",
    "custom_fields": {
        "demo_custom_field": null
    },
    "tags": [],
    "parent_bay": null
}

If we only needed to retrieve the name and URL for each device, we could modify the get_nautobot_devices function to discard all of the other data. But then we wouldn’t be able to reuse this function for other use cases where we might need a different set of fields. This is a perfect opportunity to convert get_nautobot_devices into a generator.

Example: Scalable Network Data Collection

To turn our example get_nautobot_devices function into a generator, we can simply remove the return statement and add yield statements instead. This will allow us to iterate over the devices one chunk at a time, without storing all of the data in memory at once. Note that since we are yielding from another iterable (a list of “results” in this case), we must use the yield from statement. The yield from statement tells Python to yield all of the values in the provided iterable one by one. In this case, the Nautobot API is returning pages of 50 devices at a time so we are storing the data for at most 50 devices in memory at once. The chunk size may need to be adjusted based on individual use cases.

def get_nautobot_devices():
    data = requests.get(
        "https://demo.nautobot.com/api/dcim/devices",
        headers=TEST_API_HEADERS,
        timeout=60,
    ).json()
    yield from data["results"]  # <-- Yield the first set of devices
    while data["next"]:
        data = requests.get(data["next"], headers=TEST_API_HEADERS, timeout=60).json()
        yield from data["results"]  # <-- Yield the next set of devices

for device in get_nautobot_devices():
    print(device["name"], device["url"])

Comparison

This example was tested against a Nautobot instance with 900 devices. The function that compiled a list of all devices consumed around 5MB of memory, while the generator consumed only 1MB. The generator will generally use the same amount of memory regardless of the number of devices, while the memory consumption of the list will increase linearly with the number of devices.

Code Execution Diagrams

This first diagram illustrates how a “for loop” interacts with the function that returns a list. The list is created once and then the “for loop” iterates over the devices one at a time, fetching the next device from the list each time. You can see how this example has to compile the entire list of devices before the loop can start iterating over them.

nautobote device

The next diagram illustrates how the loop interacts with the generator. The code switches back and forth between the generator and the “for loop” as it iterates over the devices.

loop

Conclusion

In large-scale network automation, memory management is crucial to maintaining performance and avoiding system crashes. By leveraging Python generators, we can significantly reduce memory consumption when dealing with large datasets, making our automation code more scalable and efficient. The example with Nautobot’s REST API clearly illustrates how generators yield memory savings by fetching data lazily, one page at a time, instead of storing everything in memory.

Identifying Memory-Heavy Code

Before optimizing with generators, it’s important to identify areas of the code that may be causing memory issues. A good starting point is to look for large data structures that are fully loaded into memory, such as lists or dictionaries, especially in loops or recursive calls.

You can use tools like grep to scan the codebase for common patterns that may be good candidates for optimization. For example:

Find loops that call functions: If the called function returns a list, it could potentially be converted into a generator.

grep -rn "^ *for.*():" /path/to/your/code

Find append, extend, or update operations in loops: This is a common pattern where a list or dictionary is incrementally built up, possibly consuming a lot of memory.

grep -rn "\(append\|extend\|update\)(" /path/to/your/code

Pitfalls to Avoid

When using generators, be aware of the following pitfalls:

  • Don’t call the generator multiple times: Each time you call a generator, it will start from the beginning. Calling the generator multiple times will increase the processing time, which may not be necessary.
  • Don’t store the generator’s output in a variable: If you store the output of the generator in a variable (devices = list(get_nautobot_devices())), Python will loop through the entire generator and store its output in a list, negating any potential memory savings. Instead, use the generator directly in a loop or comprehension.

Next Steps

If you identify any memory-heavy areas in your code, consider refactoring functions that return large datasets into generators. This will allow you to process large amounts of data in a more memory-efficient way, as demonstrated in the Nautobot API example.

Incorporating generators into your network automation tools can ensure that your code scales efficiently, even in environments with hundreds or thousands of devices. Consider applying this approach to your own projects and experience firsthand the performance benefits.

Additional Resources

-Gary



ntc img
ntc img

Contact Us to Learn More

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

Introducing Nautobot’s Floor Plan Application

Blog Detail

Nautobot is the Source of Truth for your network and a platform that supports an application ecosystem. The most recent addition to the Nautobot Application Ecosystem is the Nautobot Floor Plan Application. Simply put, this application allows firms to map out and plan their physical space requirements, in a top-down view format, in locations such as data centers and colocation cages.

Benefits of Data Aggregation/Consolidation in the Floor Plan App

Having all your data that describes your desired network state in one place has a few advantages, including allowing users to expose relationships between different objects and easily access related data about a given object. These capabilities are not possible if the various data types are kept in disparate systems.

Nautobot can act as an aggregation layer across all the authoritative data sources in your environment.

The Nautobot Floor Plan Application itself has two tremendous benefits:

  • It allows a firm to model, understand, and plan its floor space.
  • It exposes relationships and allows easy navigation between the component racks and spaces in the floor plans and objects like the rack elevation drawing, device information, and site information.

This post will discuss and elaborate on these benefits.

Example Use Case Study

The Floor Plan Application aids in data center management by providing the ability to create a gridded representation of the floor plan of a Nautobot Location and indicate the relative location of each Nautobot Rack within that floor plan.

This example floor plan, below, created with the app, shows a lot of useful information:

  • Two rows of racks (dark green) with the rack numbers and amount of rack units consumed (ex: 3 / 48 RU)
  • A cold aisle (blue) between the two rack rows
  • Hot aisles (orange) on the outside of each rack row
  • Racks ams01-101 through ams01-104 face to the right (indicated by the white gap on each grid space)
  • Racks ams01-105 through ams01-108 face to the left (indicated by the white gap on each grid space)
  • An unusable space in grid 4, 1 (X, Y) (gray)
  • A planned rack ams01-109 in space 4, 6 (light green) that will face left (indicated by the black gap on the grid space)

Also notice that the app allows users to create multi-tile objects. In the example floor plan, the cold aisle spans four tiles vertically, as described by the Y size parameter below:

Leveraging the Floor Plans in Nautobot

Having all the data for your desired network state in Nautobot makes it easier for us, as humans, to understand what is often a very complex environment. Let’s look at a scenario using our prior floor plan example.

In this scenario, the user has leveraged the Floor Plan App to model out the floor plan in the ams01 site and wants to better understand the equipment deployed in the ams01 racks shown.

Easy navigation in Nautobot from one object to related objects helps people understand what is typically a complex environment.

The layout shows that rack ams01-102 has 3 of the 48 available rack units occupied:

To further explore, the user can click in the middle of the ams01-102 grid square (representing a tile) to be taken to the rack’s home page in Nautobot. Once here, there is a wealth of useful information in the Rack panel:

  • The quantity of devices in the rack
  • The rack’s Tenant (owner)
  • The space and power utilization in the rack
  • To the right of the Rack panel there are rack elevation drawings for the front and back of the rack, displaying which rack units are occupied by which objects.
    • Clicking on an object on the rack elevation drawing will take you to the home page for that object.
  • The user sees that ams01-edge-02 is taking up rack units 40-41.
  • The user clicks on the image of ams01-edge-02 in the elevation drawing to go to the home page for that device.
  • The page shows details about the device, such as Device Type, Role, and management IP address.

Easily navigating between these related objects saves users time and effort and helps to tie together the different aspects of, and data types involved in, activities such as space and power planning. This association of related objects can also simplify your automation infrastructure because it exposes the related fields and Custom Relationship via the GraphQL API. This allows you to construct a GraphQL query that is able to traverse both related fields and Custom Relationships to provide a more meaningful response.

Easily associating an object to other related objects in Nautobot also simplifies your automation infrastructure.

Creating and Editing the Floor Plans

The creation of floor plans is explained very well in the app’s documentation.

Why Is This Valuable?

The Floor Plan Application allows firms to effectively understand and plan their physical spaces in Nautobot because it not only models out the physical space, but also allows the users and automation to quickly navigate to other related data, such as power utilization in a given rack or a specific device model in a specific rack unit. Additionally, Nautobot’s automation features, such as robust RESTful APIs and GraphQL interface, allow for immersive programmatic integration with a firm’s automation infrastructure, allowing the firm to work toward fully automating space and power planning workflows, including running space and power reports for specific pods, rows, and racks.

Where to Find Nautobot’s Floor Plan Application

Code

The Nautobot Floor Plan Application code repository can be found on GitHub.

Docs

The documentation is here. This also includes installation instructions.

Want to Try It Out?

The Nautobot Floor Plan App is available to demo online in Network to Code’s Nautobot public sandbox environment. Once you log in (the user/password are shown in the blue banner when you access the URL), navigate from the top-level menu Organization –> Location Floor Plans. From here you can select a specific site and view the floor plan.

NOTE: To scroll your browser window so that the floor plan is fully in view, click and drag the scroll bar on the right side of your browser window. Using your scroll wheel when the cursor is over any part of the floor plan will zoom in or out rather than scrolling the window. Once you are zoomed in, you can click and drag to scroll.

From there you can do things like:

  • Click on the ‘+’ or pencil icon on a square (tile) to edit a square’s attributes
    • Add a specific rack in the square.
    • Specify a status (reserved, hot aisle, code aisle, unusable, etc.) for the square.
    • Specify a rack’s orientation in the square.
  • Navigate to Organization –> Statuses to view, create, update, or delete statuses for use on the Floor Plans.

You can also join NTC’s public Slack channel and ask questions about the Floor Plan App in the #nautobot channel.

Wrapping Up

The Nautobot Floor Plan Application brings exciting and powerful capabilities to firms that:

  • Want a convenient tool to manage their physical floor space
  • Want a floor space planning tool that will integrate easily into an automated planning workflow; examples include:
    • Automated space and power reports for a given pod, site, row, or rack
    • Automated site floor layout planning Jobs
  • Want to leverage Nautobot as a Source of Truth and understand that the more data Nautobot holds, the more value it provides

We are very excited to start talking with current and potential Nautobot users about this application; we hope that you are excited to try it out and that it delivers value to you and your firm!

Thank you, and have a great day!

-Tim



ntc img
ntc img

Contact Us to Learn More

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

Introducing Nautobot v2.3.0

Blog Detail

Nautobot v2.3.0 has just been released, and we’re excited to share some of the new features and important changes that it includes!

Cloud Modeling

Nautobot v2.3.0 adds a new built-in “Cloud” app with four initial data models (CloudAccountCloudResourceTypeCloudNetwork, and CloudService). This app and models are designed specifically for the purpose of representing the cloud-based parts of your network in Nautobot, such as Amazon Web Services (AWS) and Google Cloud Virtual Private Clouds (VPCs), Azure VNETs, Application Load Balancers (ALBs) and Network Load Balancers (NLBs), and so forth.

Modular Devices

With Nautobot v2.3.0, you can now model modular network hardware in greater detail than ever before with the new, nestable ModuleBayTemplateModuleBayModuleType, and Module data models. With these, you can track individual line cards and route processors (including “cold standby” hardware not presently installed into a device), directly define interfaces and other port types as belonging to a specific module within the larger device, and even model your network hardware down to the level of individual SFP transceiver modules if you so desire.

Object Metadata

A new set of functionality designed specifically for the needs of enterprise users, Object Metadata lets you flexibly define and track information about your network data in Nautobot, such as its provenance, ownership, and classification. For example, you might use this feature to track, on a per-record or even per-field basis:

  • Who is responsible for the accuracy of this data?
  • When (after what date) can this data be deleted?
  • What is the classification or risk level of this data?
  • Which system of record is this data originally sourced from?

Object Metadata will be especially useful to users in highly regulated industries and those with significant compliance and/or auditing mandates, as it can be used for data security, data orchestration, and data attestation applications. This functionality was previously only available via custom App development, but it has been requested by our users and customers often enough that we decided to go ahead and implement it as a core Nautobot feature.

Shareable Saved Views

Do you have that Device table configured just the way you like it, with specific columns shown or hidden, specific sorting, custom filters, and pagination? Now you can save sets of display preferences for any object table within Nautobot as “saved views”. Users can define multiple saved views for a given table, switch between them with ease, and even share their saved views with other users, allowing teams to define a shared user interface (UI) for their specific workflows. Furthermore, Nautobot administrators can even set a specific saved view as the default for a given view, which users can begin from to further customize Nautobot’s UI to their own liking.

Enhanced Dynamic Groups

A third type of Dynamic Groups, “static” groups, are now available in Nautobot v2.3.0. The existing two types of groups (filter-defined and set-operation-defined) are still fully supported, but this third type is defined by directly assigning individual records to the group, providing the most granular control yet over Dynamic Group membership. These new groups have the same data model and APIs as the other group types, meaning that your Jobs and Apps (such as Nautobot Golden Config) can use these new groups immediately, with no updates necessary.

Additionally:

  • You can now assign a tenant and/or tags to each Dynamic Group.
  • The performance of Dynamic Group resolution and members lookup has been greatly improved thanks to a redesigned caching implementation.
  • Dynamic Groups are now supported by many more of Nautobot’s object types than ever before.

Interface Roles

A long-requested feature, Nautobot v2.3.0 now lets you assign user-definable Roles to individual device Interfaces (including virtual machine interfaces) for purposes of classification and configuration management.


Conclusion

We’ve covered many of the high points of this release above, but there’s so much more to Nautobot v2.3.0, including dozens of bug fixes, UI enhancements, and additional features. As always, you can read the release notes for full details, and try out the latest Nautobot release at demo.nautobot.com to see these features in action.

-Glenn Matthews



ntc img
ntc img

Contact Us to Learn More

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