lldpd: run on physicals and expose neighbour topology as the lldp fact #513

Merged
benvin merged 1 commits from benvin/lldpd into develop 2026-08-08 18:41:51 +10:00
Owner

Why

LLDP is the only source of physical switch/port topology in the estate: which switch and which port each machine is cabled to exists nowhere else (not in intent YAML, not in any other fact, not in DHCP/DNS). NetBox needs it to record device interface -> switch/port connections. This installs and enables lldpd on physical hosts and exposes its neighbour data as a structured lldp fact for the terraform-infra pdbmux backfill to consume (linked issue below).

Changes

  • Add profiles::lldpd: install the lldpd package and enable+start the service (it ships disabled), the service subscribing to the package.
  • Assign it physicals-only via hiera_include in hieradata/virtual/physical.yaml (facts.virtual == 'physical'), merged unique with the common hiera_include. VMs never receive it.
  • Add the lldp custom fact (modules/libs/lib/facter/lldp.rb): parse lldpctl -f json0 into a per-interface map of {neighbor_chassis_name, neighbor_chassis_mac, neighbor_chassis_descr, neighbor_port_id, neighbor_port_descr, vlan_id, vlan_name}, skipping interfaces with no neighbour. Confined to physical Linux hosts that have lldpctl and a live lldpd socket; returns an empty hash on any error so it can never break a puppet run.

json0 is used because it array-wraps every node regardless of cardinality, so one neighbour and many neighbours parse identically (plain keyvalue folds the neighbour SysName into the key path; plain json collapses single-element arrays into objects).

Sample fact output (prodnxsr0019, enp1s0)

{
  "enp1s0": {
    "neighbor_chassis_name":  "sg3429x-m2-02",
    "neighbor_chassis_mac":   "b8:fb:b3:a7:f9:5d",
    "neighbor_chassis_descr": "48-Port Gigabit Smart Managed Pro Switch",
    "neighbor_port_id":       "24",
    "neighbor_port_descr":    "two-gigabitEthernet 1/0/11",
    "vlan_id":                "201",
    "vlan_name":              "fabric_common_02"
  }
}

Consumer

NetBox population of the switch/port cabling is tracked in terraform-infra: unkin/terraform-infra#7 (extend the pdbmux backfill / PR #6 to read this fact and emit the connection).

https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT

## Why LLDP is the only source of physical switch/port topology in the estate: which switch and which port each machine is cabled to exists nowhere else (not in intent YAML, not in any other fact, not in DHCP/DNS). NetBox needs it to record device interface -> switch/port connections. This installs and enables lldpd on physical hosts and exposes its neighbour data as a structured `lldp` fact for the terraform-infra pdbmux backfill to consume (linked issue below). ## Changes - Add `profiles::lldpd`: install the `lldpd` package and enable+start the service (it ships disabled), the service subscribing to the package. - Assign it physicals-only via `hiera_include` in `hieradata/virtual/physical.yaml` (`facts.virtual == 'physical'`), merged `unique` with the common `hiera_include`. VMs never receive it. - Add the `lldp` custom fact (`modules/libs/lib/facter/lldp.rb`): parse `lldpctl -f json0` into a per-interface map of `{neighbor_chassis_name, neighbor_chassis_mac, neighbor_chassis_descr, neighbor_port_id, neighbor_port_descr, vlan_id, vlan_name}`, skipping interfaces with no neighbour. Confined to physical Linux hosts that have `lldpctl` and a live lldpd socket; returns an empty hash on any error so it can never break a puppet run. json0 is used because it array-wraps every node regardless of cardinality, so one neighbour and many neighbours parse identically (plain `keyvalue` folds the neighbour SysName into the key path; plain `json` collapses single-element arrays into objects). ## Sample fact output (prodnxsr0019, enp1s0) ```json { "enp1s0": { "neighbor_chassis_name": "sg3429x-m2-02", "neighbor_chassis_mac": "b8:fb:b3:a7:f9:5d", "neighbor_chassis_descr": "48-Port Gigabit Smart Managed Pro Switch", "neighbor_port_id": "24", "neighbor_port_descr": "two-gigabitEthernet 1/0/11", "vlan_id": "201", "vlan_name": "fabric_common_02" } } ``` ## Consumer NetBox population of the switch/port cabling is tracked in terraform-infra: https://git.unkin.net/unkin/terraform-infra/issues/7 (extend the pdbmux backfill / PR #6 to read this fact and emit the connection). https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
unkinben added 1 commit 2026-08-08 18:34:12 +10:00
lldpd: run on physicals and expose neighbour topology as the lldp fact
ci/woodpecker/pr/ruby-validate Pipeline was successful
ci/woodpecker/pr/puppet-lint Pipeline was successful
ci/woodpecker/pr/yamllint Pipeline was successful
ci/woodpecker/pr/bolt-validate Pipeline was successful
ci/woodpecker/pr/erb-validate Pipeline was successful
ci/woodpecker/pr/epp-validate Pipeline was successful
ci/woodpecker/pr/ruby-check Pipeline was successful
ci/woodpecker/pr/puppet-validate Pipeline was successful
eb87d4b49b
LLDP is the only source of physical switch/port topology in the estate; NetBox
has no other way to learn which switch and port each machine is cabled to. Run
lldpd on physical hosts and surface its neighbour data so the terraform-infra
pdbmux backfill can populate NetBox interface connections.

- add profiles::lldpd installing the lldpd package and enabling+starting the
  service (it ships disabled), the service subscribing to the package
- assign it physicals-only via hiera_include in hieradata/virtual/physical.yaml
  (facts.virtual == 'physical'), merged unique with the common hiera_include; VMs
  never get it
- add the `lldp` custom fact (modules/libs/lib/facter/lldp.rb) parsing
  `lldpctl -f json0` into a per-interface map of neighbour chassis/port/vlan,
  skipping interfaces with no neighbour; confined to physical Linux hosts with
  lldpctl and a live lldpd socket, and never raising (empty hash on any error)

Claude-Session: https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
benvin merged commit 499251575d into develop 2026-08-08 18:41:51 +10:00
benvin deleted branch benvin/lldpd 2026-08-08 18:41:51 +10:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unkin/puppet-prod#513