Skip to content
LinkState
Go back

Modeling MLAG, chassis, and virtual pairs honestly

Introduction to NetBox/Nautobot Modeling

NetBox and its fork Nautobot are open‑source IPAM/DCIM platforms. Core DCIM models include:

Both platforms expose a REST API, a GraphQL endpoint, and the Python client library pynautobot. Nautobot adds a job framework and app‑extension model for custom validation and UI extensions without altering core code.

Importance of Accurate Network Modeling

An accurate model is the foundation for:

Misrepresenting relationships can lead to automation that applies changes to the wrong device, creates false dependency graphs, or triggers unnecessary rollbacks. Therefore, any modeling approach must preserve the real failure domain of each physical unit while still providing a convenient abstraction for operational workflows.


MLAG Peers Representation

Virtual Devices (Discouraged)

A virtual device has no physical counterpart (no device type, no serial number). Using it to represent an MLAG pair:

  1. Failure‑domain collapse – automation may treat the pair as a single entity and ignore per‑member health checks.
  2. Limited attribute inheritance – interfaces, power ports, and console ports must be duplicated manually.

Virtual devices are only appropriate for service‑level objects that are never used for device‑level automation.

Keep each switch as a distinct Device record and create a Device Relationship of type mlag-peer. This preserves individual failure domains while providing a queryable link.

Key properties

API Example

import pynautobot

nb = pynautobot.api(
    url="https://netbox.example.com",
    token="my_api_token"
)

device_a = nb.dcim.devices.get(name="leaf01")
device_b = nb.dcim.devices.get(name="leaf02")
mlag_rel_type = nb.extras.relationship_types.get(name="mlag-peer")

nb.dcim.device_relationship.create(
    relationship_type=mlag_rel_type.id,
    source_device=device_a.id,
    destination_device=device_b.id,
    custom_fields={"mlag_domain_id": 101}
)

Assumptions & Limitations


Stacked Systems Representation

Why Device Bays Are Misleading

A Device Bay models a physical slot (e.g., a line card). True stacking (Cisco StackWise, Juniper Virtual Chassis) connects separate physical devices via stack cables or a backplane, not via a bay. Using bays implies a parent‑child containment hierarchy that does not exist electrically and can skew power/cooling calculations.

Faithful Approach: Devices + Relationships (+ Optional Components)

  1. Create a Device record for each physical switch in the stack.
  2. Identify stack‑interconnect interfaces (e.g., Te1/0/1, Te1/0/2 on a Cisco 3850).
  3. Optionally create a custom component type (e.g., “Stack Port”) and attach it to those interfaces for clearer labeling.
  4. Create a stack-member Device Relationship between every pair of devices (or hub‑spoke from a designated master to all members).

API Example

import pynautobot

nb = pynautobot.api(
    url="https://netbox.example.com",
    token="my_api_token"
)

switch_a = nb.dcim.devices.get(name="switch01")
switch_b = nb.dcim.devices.get(name="switch02")
stack_rel_type = nb.extras.relationship_types.get(name="stack-member")

nb.dcim.device_relationship.create(
    relationship_type=stack_rel_type.id,
    source_device=switch_a.id,
    destination_device=switch_b.id,
    custom_fields={"stack_id": 1}
)

Assumptions & Limitations


Clustered Firewalls Representation

Clustering (Palo Alto HA pair, Cisco ASA cluster, Fortinet FGCP) is analogous to MLAG: multiple devices act as a single logical security gateway while remaining distinct hardware.

Modeling Pattern

API Example

import pynautobot

nb = pynautobot.api(
    url="https://netbox.example.com",
    token="my_api_token"
)

fw_a = nb.dcim.devices.get(name="fw-01")
fw_b = nb.dcim.devices.get(name="fw-02")
cluster_rel_type = nb.extras.relationship_types.get(name="cluster-member")

nb.dcim.device_relationship.create(
    relationship_type=cluster_rel_type.id,
    source_device=fw_a.id,
    destination_device=fw_b.id,
    custom_fields={
        "virtual_ip": "10.0.0.1",
        "ha_sync_interface": "eth1",
        "ha_mode": "active-passive"
    }
)

# Optional tagging for bulk ops
tag = nb.extras.tags.get(name="clustered-firewall")
if tag:
    fw_a.tags.add([tag.id])
    fw_b.tags.add([tag.id])
    fw_a.save()
    fw_b.save()

Assumptions & Limitations


Virtual Chassis Representation

A Virtual Chassis (VC) (Juniper EX/QFX, Aruba VSF) binds multiple switches into a single logical control plane while retaining individual line cards and power supplies.

Modeling Options

  1. Master/Member Relationship – designate one device as the VC master and create a vc-member relationship from the master to each member.
  2. Device Roles – assign roles such as vc-master and vc-member to differentiate function.
  3. Optional Virtual Device – some teams create a virtual device to represent the VC as a whole for service‑level mapping; label it clearly and avoid using it for device‑level automation.

Roles + Relationships Workflow

API Example (Master‑Centred)

import pynautobot

nb = pynautobot.api(
    url="https://netbox.example.com",
    token="my_api_token"
)

master = nb.dcim.devices.get(name="vc-master01")
member = nb.dcim.devices.get(name="vc-member02")
vc_rel_type = nb.extras.relationship_types.get(name="vc-member")

nb.dcim.device_relationship.create(
    relationship_type=vc_rel_type.id,
    source_device=master.id,
    destination_device=member.id,
    custom_fields={"vc_priority": 100, "vc_id": 1}
)

# Assign roles (optional but useful for filtering)
master_role = nb.extras.device_roles.get(name="vc-master")
member_role = nb.extras.device_roles.get(name="vc-member")
master.role = master_role
member.role = member_role
master.save()
member.save()

Assumptions & Limitations


Troubleshooting Common Issues

Symptom: Automation Applies Changes to the Wrong Device

rels = nb.dcim.device_relationship.filter(
    relationship_type__name="mlag-peer",
    source_device__name="leaf01"
)
for r in rels:
    print(f"{r.source_device.name} -> {r.destination_device.name}")

Symptom: Missing or Duplicate Relationships

all_rels = nb.dcim.device_relationship.filter(
    relationship_type__name="stack-member",
    destination_device__name="switch02"
)

Symptom: Tags Not Reflecting Expected Groupings

tagged = nb.dcim.devices.filter(tag="clustered-firewall")

By adhering to the patterns above—distinct Device records linked via typed Relationships, complemented by Roles and Tags where appropriate—you preserve real failure domains while gaining the abstractions needed for automation, incident response, and capacity planning.


Share this post on:

Previous Post
NetworkPolicy intent versus real enforcement after encapsulation
Next Post
Running centralized and distributed gateways during migration