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 Attribute | Mapping Path | Nornir Property |
|---|---|---|
| Direct attribute | name | host.name |
| Nested relation | primary_address.address | host.hostname |
| Deep nesting | site.location.coordinates | host.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:
- Flatten nested structures: Navigate relationships to extract values
- Apply type conversions: Convert IP objects to strings
- Handle missing data: Use defaults or skip mappings
- Preserve metadata: Store full node data for tasks
Step 3: group generation
Groups are created from:
- Explicit groups: Infrahub
CoreStandardGroupmemberships - Dynamic groups: Generated from
group_mappingsconfiguration - 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:
- Pagination: Automatic handling of large result sets
- Selective fetching: Only request needed attributes
- 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
- Missing Hosts: Check filters and node kind
- Empty Groups: Verify relationship paths exist
- Mapping Errors: Ensure attributes are populated
- 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
- Understanding Nornir-Infrahub Integration - High-level concepts
- Configuring Schema Mappings - Practical mapping guide
- Infrahub Schema Documentation - Schema design guide