Skip to main content

Infrahub Inventory Concepts

This topic provides a deep understanding of how Infrahub's graph database model maps to Nornir's inventory concepts. Understanding these mappings is essential for designing effective automation workflows.

Introduction

The InfrahubInventory plugin bridges two different paradigms:

  • Infrahub: Graph database with nodes, relationships, and attributes
  • Nornir: Host-centric inventory with groups and connection parameters

This document explores:

  • How graph concepts translate to inventory structures
  • The role of schema mappings in this translation
  • Dynamic group generation from relationships
  • Performance considerations and best practices

Core mapping concepts

Nodes to hosts

In Infrahub, devices are nodes in the graph. The inventory plugin transforms these into Nornir hosts:

Infrahub Node                    Nornir Host
┌─────────────────────┐ ┌─────────────────────┐
│ InfraDevice │ ──▶ │ Host │
│ - id: abc123 │ │ - name: router1 │
│ - name: router1 │ │ - hostname: 10.1.1.1│
│ - primary_address │ │ - platform: ios │
│ └─▶ 10.1.1.1 │ │ - groups: [...] │
└─────────────────────┘ └─────────────────────┘

Attributes to properties

Node attributes become host properties through schema mappings:

Infrahub AttributeMapping PathNornir Property
Direct attributenamehost.name
Nested relationprimary_address.addresshost.hostname
Deep nestingsite.location.coordinateshost.data['coordinates']

Relationships to groups

Infrahub relationships automatically generate Nornir groups:

# Infrahub relationship
device.site.name = "chicago"
device.role.name = "spine"

# Becomes Nornir groups
host.groups = ["site__chicago", "role__spine"]

The inventory loading process

Step 1: query construction

The plugin builds a GraphQL query based on your configuration:

query {
InfraDevice(status__value: "active") {
edges {
node {
id
name { value }
primary_address {
node {
address { value }
}
}
site {
node {
name { value }
}
}
member_of_groups {
edges {
node {
name { value }
}
}
}
}
}
}
}

Step 2: data transformation

Retrieved data undergoes transformation:

  1. Flatten nested structures: Navigate relationships to extract values
  2. Apply type conversions: Convert IP objects to strings
  3. Handle missing data: Use defaults or skip mappings
  4. Preserve metadata: Store full node data for tasks

Step 3: group generation

Groups are created from:

  1. Explicit groups: Infrahub CoreStandardGroup memberships
  2. Dynamic groups: Generated from group_mappings configuration
  3. Hierarchical groups: Groups can have parent groups

Advanced inventory concepts

Filtering at source

Reduce data transfer by filtering in Infrahub:

host_node:
kind: "InfraDevice"
filters:
status__value: "active"
site__name__in: ["chicago", "dallas"]
role__name__not: "decommissioned"

This translates to GraphQL filters, ensuring only relevant nodes are fetched.

Include optimization

Minimize query complexity with selective includes:

host_node:
kind: "InfraDevice"
include:
- "name"
- "primary_address"
- "platform"
# Only fetch what you need

Relationship cardinality

Understanding cardinality is crucial:

  • One-to-One: Device → Primary Address (direct mapping)
  • One-to-Many: Device → Interfaces (requires special handling)
  • Many-to-Many: Devices ↔ Services (complex relationships)

Currently, the plugin handles one-to-one and many-to-one relationships directly.

Performance considerations

Query optimization

Large inventories require optimization:

  1. Pagination: Automatic handling of large result sets
  2. Selective fetching: Only request needed attributes
  3. Relationship depth: Limit traversal depth

Caching strategy

The plugin implements intelligent caching:

# First access fetches from Infrahub
nr = InitNornir(inventory={"plugin": "InfrahubInventory"})

# Subsequent operations use cached data
nr.filter(platform="ios") # No new query
nr.run(task=my_task) # Uses cached inventory

Parallel processing

Inventory loading is optimized for parallel task execution:

  • Host data is independent
  • Groups are pre-computed
  • No shared state between hosts

Dynamic inventory patterns

Environment-based inventory

Use branches for environments:

def get_inventory(environment="production"):
branch_map = {
"production": "main",
"staging": "staging",
"development": "dev"
}

return InitNornir(
inventory={
"plugin": "InfrahubInventory",
"options": {
"branch": branch_map.get(environment, "main")
}
}
)

Role-based filtering

Create specialized inventories:

# Core network devices only
core_nr = InitNornir(
inventory={
"plugin": "InfrahubInventory",
"options": {
"host_node": {
"kind": "InfraDevice",
"filters": {
"role__name__in": ["spine", "core-router"]
}
}
}
}
)

Multi-vendor support

Handle different device types:

# Separate node types per vendor
schema_mappings:
- name: "hostname"
mapping: "primary_address.address"
- name: "platform"
mapping: "platform.nornir_platform"
- name: "vendor"
mapping: "platform.manufacturer.name"

# Groups by vendor automatically
group_mappings:
- "platform.manufacturer.name"

Troubleshooting inventory issues

Debug mode

Enable detailed logging:

import logging
logging.basicConfig(level=logging.DEBUG)

nr = InitNornir(inventory={"plugin": "InfrahubInventory"})
# Watch for GraphQL queries and responses

Validation queries

Test your mappings with GraphQL:

from infrahub_sdk import InfrahubClient

client = InfrahubClient()
query = """
query {
InfraDevice {
edges {
node {
name { value }
primary_address {
node {
address { value }
}
}
}
}
}
}
"""
result = client.query(query)
print(result)

Common issues

  1. Missing Hosts: Check filters and node kind
  2. Empty Groups: Verify relationship paths exist
  3. Mapping Errors: Ensure attributes are populated
  4. Performance: Reduce included attributes

Integration with Nornir ecosystem

Custom inventory data

Extend with additional data:

def enrich_inventory(nr):
"""Add custom data to hosts after loading."""
for host in nr.inventory.hosts.values():
# Add computed properties
host.data["is_core"] = "core" in host.name

# Add external data
host.data["monitoring_enabled"] = check_monitoring(host.name)

return nr

Transform functions

Apply transformations during loading:

class CustomInfrahubInventory(InfrahubInventory):
def load(self):
inventory = super().load()

# Custom transformations
for host in inventory.hosts.values():
# Standardize platform names
if host.platform == "cisco_ios":
host.platform = "ios"

return inventory

Further reading